Resumen de Computer Architecture

Anuncio
Resumen del libro "Computer Architecture a Quantitative Approach"
Capítulo 2 - Principios del Set de Instrucciones
2.1 - Introducción
El set de instrucciones es una porción de la computadora visible para el programador o el compilador.
Este capítulo analiza los cinco puntos:
1.
2.
3.
4.
5.
Taxonomías de distintos sets de instrucciones. Alternativas, ventajas y desventajas.
Mediciones de los sets de instrucciones, independientes de los distintos sets.
Set de instrucciones para DSP (Digital Signal Processor) y Aplicaciones Embebidas.
Conflictos entre lenguajes, compiladores y sets de instrucciones.
Análisis del set de instrucciones MIPS, para arquitectura RISC.
Los benchmarks son una serie de pruebas/mediciones de las distintas arquitecturas. Las aplicaciones
se dividen en tres áreas distintas:



Computadoras de escritorio: Enfatiza la performance de programas con números enteros y
punto flotante, con poco incapié en el tamaño del programa y consumo de energía.
Servidores: usados principalmente para bases de datos y aplicaciones web. La performance de
operaciones con punto flotante es menos importante que con enteros y cadenas de texto
(aunque todos tienen instrucciones de punto flotante).
Aplicaciones embebidas: valoran costo y potencia (consumo de energía), por lo que el tamaño
del código es importante (usar menos memoria es más barato y consume menos). Instrucciones
de punto flotante pueden ser opcionales. Los DSP y Media Processors enfatizan performance en
tiempo real. Muchas veces se pone como objetivo el peor escenario para segurar una ganartía
para tiempo real. Si bien suelen utilizar quirks al programar determinados kernels, las
aplicaciones embebidas suelen ser cada vez más de uso general (hay sistemas operativos y
funcionalidades más avanzadas).
2.2 Clasificando los Sets de Instrucciones
El tipo de almacenamiento interno en un procesador es la diferenciación más básica. Los típicos son
stack, acumulador y set de registros. Los operadores pueden ser explícitos o implícitos. Los
operadores están implícitos en la arquitectura de stack (TOS: Top of Slack). En la arquitectura de
acumulador, un operador está implícito en el acumulador. La arquitectura de Registros de propósito
general sólo tiene operadores explícitos, sea memoria o registros. Según el tipo de arquitectura y la
operación, los operadores pueden acceder directamente a la memoria o puede que necesiten cargar los
datos primero a un registro.
Hay dos tipos de arquitectura de registros. Un tipo puede acceder a la memoria como parte de una
instrucción (llamada arquitectura registro-memoria). La otra puede acceder a la memoria sólo con
instrucciones de load y store (llamada arquirectura de load-store o registro-registro). Una tercer
arquitectura no muy utilizada puede mantener todas sus operaciones en memoria y es llamada
arquitectura memoria-memoria. Anteriormente todas usaban arquitecturas de stack o acumulador.
Actualmente usan arquitecturas de registros load-store. Las razones son que los registros son más
rápidos que la memoria y más eficientes para un compilador, debido, por ejemplo, al pipeline. Más
importante aún, los registros pueden almacenar variables, lo que reduce el tráfico a/desde memoria,
aumenta la velocidad del programa y la densidad de código mejora.
Los compiladores mejoran cuando aumenta la cantidad de registros de propósito general. El dominio del
código optimizado a mano en las aplicaciones para DSP tiende a mantener muchos registros de uso
específico.
Las arquitecturas GPR (General Purpose Register) se dividen según dos características importantes.
Ambas están relacionados a la naturaleza de las típicas operaciones aritmético-lógicas (ALU):
1. Si las instrucciones de la ALU tienen dos o tres operandos. En la de tres operandos, el tercero es
para almacenar el resultado. En la de dos, uno es tanto un dato como el resultado.
2. La cantidad de operandos que pueden ser direccionados a la memoria. Este valor va de cero a
tres.
De las 7 combinaciones posibles, se destacan tres:
Registro-Registro (0, 3)
Registro-Memoria (1, 2)
Memoria-Memoria (2, 2) / (3, 3)
(...)
Apéndice A - Pipelining: Basic and Intermediate Concepts
A.1 - Introducción
Que es Pipeline?
Pipeline es una técnica de implementación donde múltiples instrucciones se solapan durante la
ejecución, tomando ventaja del paralelismo que existe entre las acciones necesarias para ejecutar una
intrucción. Cada etapa del pipeline (llamada pipe stage o pipe segment) completa una parte de la
instrucción.
Las bases de un set de instrucciones RISC



Todas las operaciones en datos se realizan en registros
Las únicas operaciones que intervienen con la memoria son las operaciones de load/store
Los formatos de instrucción son pocos en números con todas las operaciones siendo típicamente
de un sólo tamaño
Estas simples propiedades llevan a una dramática simplificación en la implementación del pipeline.
El set de instrucciones MIPS provee 32 registros (aunque el registro 0 siempre tiene el valor 0). Las
instrucciones se dividen en tres clases distintas:
Instrucciones ALU: Toman dos registros o un registro y un valor inmediato, opera con ellos y graba el
resultado en un tercer registro. Operaciones (ej): AND, SUB y operaciones lógicas.
Instrucciones de load/store: Toman un registro de origen (registro base) y un valor inmediato (offset)
como operandos. La suma de ambos (dirección efectiva) es usada como una dirección de memoria. Un
segundo registro opera como destino de los datos cargados desde memoria (instrucciones load) o como
origen de los datos para guardar en memoria (instrucciones store).
Instrucciones de Branch y Jump: Los branch son tranferencias de control condicionales. Un set de
comparaciones entre un par de registros y/o cero determinan si se hace un salto o no. El destino del
salto se obtiene sumando un offset al PC actual.
Implementación sencilla de un set de instrucciones RISC
Cada instrucción del subconjunto RISC puede ser implementada en a lo sumo 5 ciclos de reloj:
1. Instruction Fetch Cycle (IF): Envía el PC a la memoria y trae la instrucción actual desde la
memoria. Actualiza el PC incrementándolo en 4.
2. Instruction Decode/Register Fetch Cycle (ID): Decodifica la instrucción y lee los registros de
origen de datos correspondientes. Calcula la posible dirección de destino del Branch. La
decodificación se lleva a cabo en paralelo a la lectura de registros, lo que es posible gracias a
que los especificadores de registros están en una posición fija de la instrucción (fixed-field
decoding).
3. Execution/Effective Address Cycle (EX): La ALU opera sobre los operandos indicados, mediante
una de las tres operaciones:
 Referencia a memoria: La ALU suma la dirección base al offset, para obtener la dirección
efectiva.
 Instrucción ALU Registro-Registro: La ALU realiza una operación sobre los registros,
según el código de la instrucción (opcode).
 Instrucción ALU Registro-Inmediato: La ALU realiza una operación entre un registro y un
valor inmediato.
En una arquitectura load/store la dirección efectiva y el ciclo de ejecución puede ser combinada en
un único ciclo de reloj, ya que ninguna instrucción necesita simultaneamente calcular una
dirección y realizar una operación sobre la misma.
4. Memory Access (MEM): Si la instrucción es de lectura (load), la memoria realiza una lectura
usando la dirección efectiva. Si es de escritura (store) entonces la memoria escribe los datos del
segundo registro.
5. Write-Back Cycle (WB): En instrucciones ALU Registro-Registro e instrucciones Load el resultado
se escribe en el registro correspondiente, sea que viene de la memoria (load) o de la ALU.
Pipeline clásico de 5 etapas para un procesador RISC
La implementación debe asegurarse que el solapamiento de instrucciones no genere conflictos entre si.
Para ello se tienen en cuenta tres cosas:
Primero, se manejan por separado la memoria para datos e instrucciones, haciendo uso de un caché de
datos y otro de instrucciones.
Segundo, el archivo de registros es usado en dos etapas: una para leer durante el ID y otra para
escribir durante el WB. Se necesitan dos lecturas y una escritura en cada ciclo de reloj. Para manejar
las lecturas y escrituras en un mismo registro, se realiza la escritura en la primera mitad del ciclo del
reloj y se lo lee durante la segunda mitad.
Tercero, el PC debe incrementarse en cada ciclo, para el próximo IF, pero deben tenerse en cuenta los
posibles saltos de un branch, durante la etapa de ID.
Si bien es crítico asegurarse que las instrucciones en un pipeline no intenten usar un mismo recurso de
hardware a la misma vez, es necesario asegurarse que las instrucciones durante las distintas etapas del
pipeline no interfieran entre si. Esto se puede lograr introduciendo registros de pipeling entre las
sucesivas etapas, de modo que al finalizar un ciclo de reloj todos los resultados de una determinada
etapa son guardados en un registro que es usado como entrada para la próxima etapa. Los registros
son nombrados según las etapas que interconectan (ID/IF, IF/RF, RF/EX, EX/MEM, MEM/WB).
Problemas básicos de performance en un pipeline
El pipeline incrementa el rendimiento del CPU (cantidad de instrucciones realizadas por segundo), pero
no reduce el tiempo de ejecución de una instrucción individual. El tiempo requerido para mover una
instrucción a lo largo del pipeline es un ciclo de reloj. Como todas las etapas se ejecutan a la vez, la
duración de un ciclo de reloj está determinada por la etapa más lenta. La diferencias de duraciones
entre estas y un overhead del pipeline (generado por la inclusión de los registros de pipeline),
incrementan el tiempo de ejecución de una instrucción, aunque igualmente, mejora el rendimiento.
A.2 - Riesgos de Pipeline (Pipeline hazards)
Hay situaciones, llamadas hazards (riesgos), que previenen que la próxima instrucción del flujo de
instrucciones pueda ser ejecutada durante su ciclo de reloj correspondiente. Los Riesgos reducen la
performance. Hay tres tipos de riesgos:



Riesgos estructurales: surgen de conflictos donde el hardware no puede soportar todas las
combinaciones de instrucciones simultaneamente (solapadas).
Riesgos de datos: cuando una instrucción depende del resultado de una instrucción previa que
se ejecuta en paralelo.
Riesgos de control: originados por instrucciones como los branch que cambian el PC.
Estos obstáculos pueden hacer necesario detener (stall) el pipeline. Cuando una instrucción es
demorada, las instrucciones posteriores a ésta también son congeladas. Las instrucciones previas deben
continuar ejecutándose.
Performance de pipelines con stalls
Una demora hace que la performance del pipeline decaiga de la ideal.
Riesgos estructurales
La ejecución de instrucciones solapadas requiere implementar pipeline en unidades funcionales y
duplicación de recursos. Cuando una combinación de instrucciones no puede ser ejecutada
correctamente por problemas de recursos, se dice que tiene un Riesgo Estructural (Conflicto
Estructural). Generalmente ocurren cuando una unidad funcional no está completamente tuneleada (y
no se puede procesar una serie de instrucciones a una por ciclo de reloj) o cuando un recurso no fue
duplicado lo suficiente.
Cuando una secuencia de instrucciones se encuentra con este conflicto, el pipeline detiene una de las
instrucciones hasta que la unidad requerida se libere.Cuando esto ocurre, se genera una burbuja. Esta
demora hace que el CPI decaiga del valor ideal de 1. El efecto de esa burbuja es ocupar una etapa ciclo,
a medida que avanza por el pipeline. ¿Por qué un diseñador permitiría un riesgo estructural?
Principalmente, para reducir el costo de la unidad.
Riesgos de Datos
Ocurren cuando un pipeline intercambia el orden de acceso de lectura/escritura a un operando.
Ejemplo:
ADD
R1, R2, R3
SUB
R4, R1, R5
AND
R6, R1, R7
OR
R8, R1, R9
XOR
R10,R1, R11
La instrucción ADD escribe el valor de R1 en la etapa WB, pero la instrucción SUB lee el valor durante
su fase ID. Este caso es un Riesgo de Datos. La instrucción SUB va a leer un valor erroneo y tratar de
usarlo. La operación AND también estaría usado un dato erroneo. Sin embargo las instrucciones OR y
XOR no incurrirían en un riesgo de datos.
Minimizing Data Hazard Stalls by Forwarding
Forwarding (aka bypassing or short-circuiting) funciona de la siguiente manera:
1. El resultado de la ALU, tanto del registro EX/MEM como del registro MEM/WB, es alimentado de
vuelta a la entrada de las ALUs.
2. Si el hardware de forwarding detecta que la instrucción previa escribió un registro usado en la
operación actual, la lógica de control selecciona el resultado de la operación previa (la salida de
la ALU) como la entrada a la ALU para esta operación (en vez del valor leído del registro).
Riesgos de Datos que requieren demoras
Desafortunadamente no todas las Riesgos de Datos pueden ser evitados mediante forwarding. Analizar
el siguiente caso:
LD
R1, 0(R2)
SUB
R4, R1, R5
AND
R6, R1, R7
OR
R8, R1, R9
La instrucción LD no tienen la información hasta el final de su ciclo 4 (ciclo MEM), mientras que la
instrucción SUB necesita tener el dato al principio del mismo ciclo. Por ende, el riesgo de datos
generado por una instrucción Load no puede ser eliminado completamente con simple hardware. La
instrucción Load tiene un delay o latencia. Se necesita modificar el pipeline para agregarle un circuito
de bloqueo (pipeline interlock) para preservar la correcta ejecución. Este circuito detecta un riesgo de
datos y congela el pipeline (stall), generandose una burbuja. Debido a esta demora, la instrucción AND
no necesita forwarding (los datos los toma desde el registro) y la instrucción OR no necesita ningún tipo
de forwarding.
Riesgo de Salto/Control
Los riesgos de control pueden causar una mayor pérdida de performance que los riesgos de datos.
Cuando un branch es ejecutado, puede o no cambiar el PC. Si el branch cambia el PC a su dirección de
destino, se dice que el salto es tomado (taken). En caso contrario, se dice que el salto es no tomado
(not taken).
La solución más sencilla para tratar con los saltos sería: luego de detectar que la instrucción es de salto
(durante la fase de ID), repetir el IF de la instrucción posterior al branch, pero usando la dirección de
destino. De esta manera, el primer ciclo IF es esencialmente un stall. Un stall por cada branch genera
decremento en la performance de entre un 10% y 30%.
Reduciendo la penalidad del salto
Hay varios métodos para tratar con los stall causados por las instrucciones de branch. Se puede
analizar cuatro esquemas distintos, a la hora de compilar, en donde el compilador podrá tratar de
minimizar la penalidad de la demora haciendo uso del conocimiento del esquema del hardware usado y
el comportamiento de los saltos.
El esquema más sencillo es congelar (freeze) o dejar fluír (flush) el pipeline, manteniendo o eliminando
cualquier instrucción después del branch, hasta que el destino del salto se sepa. La ventaja de este
método se aprecia en su simpleza, tanto en hardware como en software. La penalidad es fija y no
puede ser reducida por software.
Más performante y apenas más complejo es tratar a cada salto como no tomado, simplemente dejando
que el hardware continúe como si el salto no fuera ejecutado. Se debe tener cuidado de no cambiar el
estado del procesador hasta que el destino del branch sea conocido.
Descargar