Procesadores RISC, multiproceso y caché

Introducción:

A lo largo de la historia de la industria de los ordenadores, la tendencia mayormente adoptada para conseguir un aumento de prestaciones, ha sido el incremento de la complejidad de las instrucciones. Es lo que se ha denominado "computación con conjuntos de instrucciones complejas" o CISC (Complex Instruction Set Computing).

Sin embargo, la tendencia actual, se esfuerza en conseguir procesadores con conjuntos de instrucciones de complejidad reducida o RISC (Reduced Instruction Set Computing). La idea es que un conjunto de instrucciones poco complejas son simples, y por tanto de más rápida ejecución, lo que permite crear un código más "aerodinámico".

Tanto la tecnología CISC como la RISC son acreditadas a IBM, aunque sus antecesores bien pueden ser John vonNeumman (inventor del primer programa de ordenador almacenado, y que promovía la velocidad inherente a conjuntos de instrucciones reducidas), Maurice Wilkes (padre de la microprogramación y de muchos conceptos de los diseños RISC), y Seymour Cray (primeros supercomputadores, empleando principios RISC).

En 1975, IBM inició el desarrollo de un controlador para un sistema de conmutación telefónica, que aunque fue abandonado, sirvió como punto de partida para el desarrollo de una CPU con tecnología ECL, corazón del sistema 801, precursor del IBM PC RT.

Los inicios de la tecnología RISC también surgen en el ambiente académico, ya que en 1980, la Universidad de Berkeley (California), el Dr. David A. Patterson inició un proyecto denominado RISC I, que obtuvo resultados en tan solo 19 meses, seguido por RISC II, SOAR (Smalltalk on a RISC) y SPUR (Symbolic Processing on a RISC). El resultado directo, además de la educación en la ingeniería y los fundamentos del diseño de microprocesadores, fue la creación de una máquina que fuese capaz de mayores velocidades de ejecución a menores velocidades de reloj y que requiriese menores esfuerzos de diseño.

Casi simultáneamente, en la Universidad de Stanford, el Dr. John Hennesy inició también un proyecto de implementación RISC, denominado MIPS, seguido por el sistema MIPS-XMP, enfocados hacia el proceso simbólico, demostrando las capacidades de velocidad de la arquitectura RISC.

Ambos profesores se vieron envueltos rápidamente, en proyectos de productos comerciales, y en concreto, Hennesy fue uno de los fundadores de MIPS Computer Systems, mientras Patterson actuaba de asesor durante el desarrollo del primer SPARC.

Por otro lado, durante las pasadas décadas, el multiproceso, como medida de incrementar drásticamente las prestaciones de los sistemas a un coste razonable, se ha visto reducido al ámbito de los computadores de "alto nivel", en parte debido a los bajos niveles de integración del silicio, y a la falta de software que facilitase la ejecución paralela de las aplicaciones.

Las ventajas de los procesadores RISC, especialmente las ligadas a los sistemas abiertos (léase UNIX), los hacen plataformas ideales para explorar los puntos fuertes de los sistemas multiprocesadores.

Tecnología RISC:

La comparación que antes hemos realizado entre CISC y RISC es algo simple, ya que no es sólo una cuestión de diferencias en el conjunto de instrucciones, puesto que es fundamental resaltar el mejor uso y aprovechamiento de los recursos del silicio, es decir, menor tiempo de diseño y empleo de menor número de transistores, lo que redunda en menor número de errores de diseño y menor tiempo de ejecución para instrucciones individuales.

Las características comunes a todos los procesadores RISC, fuente de sus capacidades de altas prestaciones, son:

  1. Modelo de conjunto de instrucciones Load/Store (Cargar/Almacenar). Sólo las instrucciones Load/Store acceden a memoria; las demás operaciones en un RISC, tienen lugar en su gran conjunto de registros. Ello simplifica el direccionamiento y acorta los tiempos de los ciclos de la CPU, y además facilita la gestión de los fallos de paginas (page faults) en entornos de memoria virtual. Además, permite un elevado nivel de concurrencia a consecuencia de la independencia de las operaciones de Load/Store de la ejecución del resto de las instrucciones.
  2. Arquitectura no destructiva de tres direcciones. Los procesadores CISC destruyen la información que existe en alguno de los registros, como consecuencia de la ejecución normal de instrucciones; esto es debido a su arquitectura de dos direcciones, por la cual el resultado de una operación sobrescribe uno de los registros que contenía a los operandos. Por contra, las instrucciones RISC, con tres direcciones, contienen los campos de los dos operandos y de su resultado. Por lo tanto, tanto los operandos origen como el destino, son mantenidos en los registros tras haber sido completada la operación. Esta arquitectura "no destructiva" permite a los compiladores organizar las instrucciones de modo que mantengan llenos los conductos (pipelines) del chip, y por tanto reutilizar los operandos optimizando la concurrencia.
  3. Instrucciones simples, de formato fijo, con pocos modos de direccionamiento. Las instrucciones simples reducen de manera muy significativa el esfuerzo para su descodificación, y favorecen su ejecución en pipelines. Las instrucciones de longitud fija, con formatos fijos, implican que los campos de códigos de operación (opcodes) y de los operandos están siempre codificados en las mismas posiciones, permitiendo el acceso a los registros al mismo tiempo que se está descodificando el código de operación. Todas las instrucciones tienen una longitud equivalente a una palabra y están alineadas en la memoria en límites de palabra (word boundaries), ya que no pueden ser repartidas en pedazos que puedan estar en diferentes páginas.
  4. Ausencia de microcódigo. El microcódigo no se presta a la ejecución en ciclos únicos, ya que requiere que el hardware sea dedicado a su interpretación dinámica. La programación en microcódigo no hace que el software sea más rápido que el programado con un conjunto de instrucciones simples. Todas las funciones y el control, en los procesadores RISC, están "cableados" (hardwired), para lograr una máxima velocidad y eficiencia.
  5. Ejecución en conductos (pipelined). Las instrucciones simples, de formato fijo y ciclo único permiten que las diferentes etapas de los ciclos de ejecución (búsqueda o fetch, descodificación, ejecución, y escritura del resultado o result write-back) para instrucciones múltiples, se puedan realizar simultáneamente, de un modo más simple y eficaz.
  6. Ejecución en ciclos únicos (single-cycle). El resultado directo de los conjuntos de instrucciones que ofrecen los procesadores RISC, es que cada instrucción puede ser ejecutada en un único ciclo de la CPU. Esto invalida la creencia de que las microinstrucciones en microcódigo, creadas para ser ejecutadas en un solo ciclo de procesador, son más rápidas que las instrucciones del lenguaje ensamblador. Ya que el caché esta construido partiendo de la misma tecnología que el almacenamiento de control del microprograma, una única instrucción puede ser ejecutada a la misma velocidad que una microinstrucción. La ejecución en ciclos únicos también simplifica la gestión de las interrupciones y los conductos (pipelines).

Conceptos de multiproceso:

La industria informática, ha tenido siempre un objetivo primordial, repetido a lo largo de toda su cadena (fabricantes de semiconductores, fabricantes de sistemas y usuarios): la búsqueda de la velocidad. Para alcanzar este objetivo se han invertido ingentes cantidades de recursos, hasta alcanzar los límites físicos del silicio.

Obviamente, la velocidad va ligada a las prestaciones, y por lo general, la primera ha sido la principal medida para decidirse por un sistema u otro. Sin embargo, por muy evidente que parezca, y dados los límites físicos de los semiconductores, las prestaciones pueden no estar forzosamente ligadas a la velocidad. Hoy es posible construir sistemas, que aún teniendo procesadores más "lentos" que otros, ofrezcan unas prestaciones significativamente superiores. Son los sistemas multiprocesador, que como su denominación indica, incorporan varios procesadores para llevar a cabo las mismas funciones.

No es un concepto nuevo, ya que los "minicomputadores" construidos por compañías como NCR, Sequent y Stratus, ya empleaban varios nodos de proceso como alternativas económicas a otros productos de otras compañías. Sin embargo, aquellos sistemas aún duplicaban recursos caros del sistema, como memoria y dispositivos de entrada/salida, y por tanto, confinaban a los sistemas multiprocesador al mundo de los sistemas de alto nivel.

Ahora, y en gran medida gracias a los procesadores de arquitectura RISC, el soporte multiprocesador es una solución integrada y fácilmente disponible en estaciones de trabajo de sobremesa, que resuelve, a través de hardware VLSI, los complejos problemas de compartición de recursos (memoria compartida) de aquellas primeras máquinas.

Evidentemente, estas mejoras en el hardware, para ser funcionales, requieren importantes desarrollos en el software, y de hecho, muchos sistemas operativos admiten extensiones multiproceso (Match, SCO, Solaris, System V, etc.), que proporcionan paralelismo "en bruto" (asignando múltiples tareas a múltiples procesadores) a nivel del sistema operativo.

Las aplicaciones escritas para facilitar el paralelismo en su ejecución, incrementan significativamente las prestaciones globales del sistema; esto es lo que se denomina multi-enhebrado (multithreading), que implica dividir una sola aplicación entre varios procesadores. Sin embargo, los desarrolladores de software y programadores de aplicaciones sólo han comenzado a explorar las vastas posibilidades de incremento de prestaciones que ofrecen los sistemas con capacidades reales de proceso en paralelo.

El multiproceso no es algo difícil de entender: más procesadores significa mas potencia computacional. Un conjunto de tareas puede ser completado más rápidamente si hay varias unidades de proceso ejecutándolas en paralelo. Esa es la teoría, pero otra historia es la práctica, como hacer funcionar el multiproceso, lo que requiere unos profundos conocimientos tanto del hardware como del software. Es necesario conocer ampliamente como están interconectados dichos procesadores, y la forma en que el código que se ejecuta en los mismos ha sido escrito para escribir aplicaciones y software que aproveche al máximo sus prestaciones.

Para lograrlo, es necesario modificar varias facetas del sistema operativo, la organización del código de las propias aplicaciones, así como los lenguajes de programación.

Es difícil dar una definición exacta de un sistema multiprocesador, aunque podemos establecer una clasificación de los sistemas de procesadores en:

SISD o secuencia única de instrucciones y datos (Single Instruction, Single Data): una sola secuencia de instrucciones opera sobre una sola secuencia de datos (caso típico de los ordenadores personales).
SIMD o secuencia única de instrucciones y múltiple de datos (Single Instruction, Multiple Data): una sola secuencia de instrucciones opera, simultáneamente, sobre múltiples secuencias de datos (array processors).
MISD o múltiples secuencias de instrucciones y única de datos (Multiple Instruction, Single Data): múltiples secuencias de instrucciones operan, simultáneamente, sobre una sola secuencia de datos (sin implementaciones útiles actualmente).
MIMD o múltiples secuencias de instrucciones y datos (Multiple Instruction, Multiple Data): múltiples secuencias de instrucciones operan, simultáneamente, sobre múltiples secuencias de datos.

Los sistemas multiprocesadores pueden ser clasificados con mayor propiedad como sistemas MIMD. Ello implica que son máquinas con múltiples y autónomos nodos de proceso, cada uno de los cuales opera sobre su propio conjunto de datos. Todos los nodos son idénticos en funciones, por lo que cada uno puede operar en cualquier tarea o porción de la misma.

El sistema en que la memoria está conectada a los nodos de proceso establece el primer nivel de distinción entre diferentes sistemas multiprocesador:

  1. Multiprocesadores de memoria distribuida (distributed-memory multiprocessors), también denominados multiprocesadores vagamente acoplados (loosely coupled multiprocessors). Se caracterizan porque cada procesador sólo puede acceder a su propia memoria. Se requiere la comunicación entre los nodos de proceso para coordinar las operaciones y mover los datos. Los datos pueden ser intercambiados, pero no compartidos. Dado que los procesadores no comparten un espacio de direcciones común, no hay problemas asociados con tener múltiples copias de los datos, y por tanto los procesadores no tienen que competir entre ellos para obtener sus datos. Ya que cada nodo es un sistema completo, por si mismo (incluso sus propios dispositivos de entrada/salida si son necesarios), el único límite práctico para incrementar las prestaciones añadiendo nuevos nodos, esta dictado por la topología empleado para su interconexión. De hecho, el esquema de interconexión (anillos, matrices, cubos, ...), tiene un fuerte impacto en las prestaciones de estos sistemas. Además de la complejidad de las interconexiones, una de las principales desventajas de estos sistemas, como es evidente, es la duplicación de recursos caros como memoria, dispositivos de entrada/salida, que además están desocupados en gran parte del tiempo.
  2. Multiprocesadores de memoria compartida (shared-memory multiprocessors), también llamados multiprocesadores estrechamente acoplados (tightly coupled multiprocessors). Son sistemas con múltiples procesadores que comparten un único espacio de direcciones de memoria. Cualquier procesador puede acceder a los mismos datos, al igual que puede acceder a ellos cualquier dispositivo de entrada/salida. El sistema de interconexión más empleado para estos casos, es el de bus compartido (shared-bus). Tener muchos procesadores en un único bus tiene el inconveniente de limitar las prestaciones del sistema a medida que se añaden nuevos procesadores. La razón es la saturación del bus, es decir, su sobre utilización; en un sistema de bus compartido, se deriva por la contienda entre los diferentes dispositivos y procesadores para obtener el control del bus, para obtener su utilización.

Arquitecturas de Multiproceso

Es evidente, que los sistemas actuales tienden al uso de arquitecturas de memoria compartida, fundamentalmente por razones de costes, a pesar del problema de la contienda por el bus. Los tres fuentes fundamentalmente responsables de dicha disputa son la memoria (cada CPU debe usar el bus para acceder a la memoria principal), la comunicación (el bus es usado por los "bus masters" para la comunicación y coordinación), y la latencia de la memoria (el subsistema de memoria mantiene al bus durante las transferencias de datos, y en función de la velocidad a la que la memoria puede responder a las peticiones, puede llegar a ser un factor muy significativo).

Los sistemas de memoria caché y el multiproceso:

Los sistemas de memoria multinivel (caché) son un esfuerzo para evitar el número de peticiones realizadas por cada CPU al bus. Los caches son pequeñas y rápidas (y por tanto caras) memorias, que hacen de tampón (buffer) entre la CPU y la memoria externa, para mantener los datos y/o instrucciones. Se basan en el principio de la "localidad", lo que significa que, dada la fundamental naturaleza secuencial de los programas, los siguientes datos o instrucciones requeridas, estarán localizadas inmediatamente a continuación de las actuales.

Los datos contenidos en la memoria caché se organizan en bloques denominados líneas. Las líneas son cargadas en el caché como copias exactas de los datos situados en la memoria externa. Para referenciar a los datos de la memoria caché, se emplean marcas (tags) que identifican a cada línea. Las marcas o tags emplean una porción de la dirección física de los datos, para compararla con la dirección física solicitada por la CPU. Cuando existe una coincidencia exacta de la dirección y de otros cualificadores (estado, privilegio, contexto, etc.), se dice que ha tenido lugar un acierto (hit) de caché; en caso contrario, tiene lugar un fallo (miss) del caché, y en ese caso, los datos han de ser recuperados desde la memoria.

El empleo de memoria caché se ha popularizado, como medida para acelerar el tiempo de acceso a la memoria principal, incluso en los sistemas monoprocesador, evitando así, según se incrementa la velocidad de los propios procesadores, aumentar la velocidad de dicha memoria, y por tanto encarecer el sistema.

La forma en que la memoria es actualizada por los caches locales puede tener un gran impacto en las prestaciones de un sistema multiprocesador. Básicamente hay dos métodos:

  1. Escritura continua (write-through). Requiere que todas las escrituras realizadas en el caché actualicen asimismo los datos de la memoria principal. De esta forma, la memoria principal siempre tiene la última copia de los datos, y por tanto no hay nunca ninguna incoherencia con el caché. El inconveniente es que se producen frecuentes accesos a memoria, especialmente superfluos cuando el software está modificando las mismas secciones de datos repetidamente (por ejemplo ejecutando bucles).
  2. Copia posterior (copy-back). Es un sistema mucho más eficiente, aunque también más complejo de implementar. En este caso, la CPU puede modificar la línea de caché sin necesidad de actualizar inmediatamente la memoria principal. Los datos sólo son copiados a la memoria principal cuando la línea de caché va a ser reemplazada con una nueva. Ello no solo minimiza el tráfico del bus, de vital importancia para el resto de los procesadores, sino que también libera al procesador de la tarea de escribir en la memoria principal. Sin embargo, este sistema, en una arquitectura de bus compartido, implica un nuevo nivel de dificultad, denominado coherencia o consistencia (coherency o consistency); dado que cada caché puede tener una copia de los datos existentes en la memoria principal, el desafío es asegurar que los datos permanecen iguales entre todos los caches. Hay dos métodos para mantener cada línea de caché idéntica a las demás:
    1. Escritura radiada (write-broadcast), que requiere que la CPU que modifica los datos compartidos actualice los otros caches, para lo cual escribe en el bus la dirección de los datos, y los datos mismos, de modo que todos los dispositivos interesados (otras CPU’s) los capturen. Esto asegura que cada línea de caché en el sistema es una copia exacta de las demás.
    2. Escritura invalidada (write-invalidate), impide a una CPU modificar los datos compartidos en su caché hasta que otros caches han invalidado sus copias. En cuanto otros caches invalidan sus líneas, el caché modificado tiene la única copia; de este modo, se garantiza que un sólo caché escribe una línea compartida en un momento dado. Tiene la ventaja de conservar el ancho de banda del bus ya que los datos modificados no tienen que ser enviados a otros caches.

Sistemas de Actualización de la Memoria

Ambos sistemas requieren que los caches sean capaces de identificar peticiones en el bus que afecten a sus datos, lo que se realiza con una técnica conocida como "sondeo del bus" (bus snooping). Cada caché compara las direcciones de las peticiones en el bus compartido con los datos en su propio cache, usando las marcas (tags).

Este sistema requiere un acceso concurrente a las marcas (tags) del caché por parte del bus del sistema y del bus del procesador. Sin dicho acceso concurrente, el procesador no podría acceder al caché durante las operaciones de sondeo del bus (que tienen que tener prioridad de acceso a las marcas, para poder mantener la coherencia del caché). El resultado son frecuentes atascos del procesador y consecuentemente, bajo rendimiento.

A su vez, hay varios protocolos asociados con el sondeo del bus para el movimiento de los datos y los mensajes entre los caches:

  1. Intervención indirecta de los datos (indirect data intervention). Es el método más simple de intercambio de datos entre procesadores, aunque también el menos eficiente. La 1ª CPU hace una petición de datos, que es sondeada por la 2ª; tiene lugar un acierto de sondeo (snoop hit) si dichos datos están en el caché de la 2ª CPU, entonces esta obtiene el control del bus e indica a la 1ª que lo reintente más tarde. La 2ª CPU escribe los datos de su caché a la memoria, y la 1ª CPU obtiene el control del bus de nuevo, reiniciando la petición. Los datos son ahora suministrados por la memoria.
  2. Intervención directa de los datos (direct data intervention). Los datos son suministrados directamente por la 2ª CPU a la 1ª. Este mecanismo es aplicable fundamentalmente a los sistemas con sistemas de cache de copia posterior (copy-back). Aunque pueden evitar muchos ciclos comparados con la intervención indirecta, la memoria principal permanece inconsistente con el caché, y debe de ser actualizada cuando la línea de caché es vaciada.
  3. Reflexión a memoria (memory reflection) con intervención directa. La memoria captura los datos que han sido depositados en el bus, mientras son enviados a la CPU solicitante. Si la línea de caché no ha sido modificada antes de que tenga lugar la última transacción de intervención de datos, no es necesario escribir a la memoria, con lo que se conserva el ancho de banda. Esta actualización del cache solicitante y la memoria puede tener lugar en un mismo y único ciclo de reloj.

Protocolos de sondeo del bus

RISC frente a CISC:

Existen varios mitos que contraponen las ventajas de la tecnología RISC frente a la CISC, que es importante descalificar:

  1. Los procesadores RISC ofrecen peor soporte para los lenguajes de alto nivel o HLL (High Level Language) que lo CISC. Esta creencia se argumenta en que un conjunto de instrucciones de "alto nivel" (CISC) es mejor soporte para lenguajes de alto nivel. Sin embargo, la característica fundamental de los lenguajes de alto nivel, implica que el programador sólo interacciona con el ordenador a través del propio lenguaje de alto nivel (programación, depuración, mensajes del sistema, etc.), por lo que todos los problemas a "bajo nivel", deben de ser transparentes y desconocidos para el. Por ello, son de nulas consecuencias para el programador y los lenguajes de alto nivel, como se implementan las funciones, en función del tipo de CPU.
  2. Es más complicado escribir compiladores RISC que CISC. Dado que los procesadores CISC tienen un mayor número de instrucciones y modos de direccionamiento, existen por tanto más formas de hacer la misma tarea, lo que puede confundir tanto al compilador como al que lo escribe. Por ello, subjetivamente es posible escoger una forma de hacerlo poco adecuada, por el tipo de instrucciones o por el tiempo de ejecución que requieren. En cambio, en un procesador RISC, hay menos opciones, por lo que el compilador es más simple, aunque se genere, habitualmente, un 20-30% más código; a cambio, se consigue un incremento de la velocidad de hasta un 500%.
  3. Un programa es más rápido cuanto más pequeño. La velocidad a la que un programa puede ser ejecutado no depende en absoluto de su tamaño, sino del tiempo de ejecución de cada una de sus instrucciones. Dado que las instrucciones RISC son más rápidas, y admiten mejor los pipelines, puede haber mayor paralelismo y simultaneidad en la ejecución de pequeñas secciones de código. Dichas secciones de código pueden ser ejecutadas en una fracción del tiempo que requiere una sola instrucción CISC.

Resumiendo:

No es un hecho meramente académico, sino puramente comercial y económico. La "era RISC" ha alcanzado a todos los fabricantes de semiconductores: AMD, Intel, MIPS, Motorola, ROSS, ...; y todos ellos son productos usados por fabricantes de ordenadores y estaciones de trabajo: Apple, DEC, HP, IBM, SUN, etc. y sus correspondientes clónicos.

El tiempo de diseño de estos productos se reduce sensiblemente, lo que disminuye su coste final, y por tanto, se incrementan sus expectativas, al poder llegar al mercado en un tiempo más adecuado, y con menos posibilidades de errores.

Además, son globalmente más eficaces, de menores dimensiones y más bajo consumo, ofreciendo siempre claras ventajas técnicas frente a los más avanzados CISC.

Actualmente, las estaciones de trabajo RISC multiprocesadoras de mayor éxito, se basan en diferentes versiones de la tecnología SPARC: superSPARC e HyperSPARC.

Esta claro que el futuro pertenece a los RISC y a los sistemas multiprocesador, a no ser que la física y la electrónica logren superar las barreras tecnológicas para incrementar muy por encima de las cotas actuales, las velocidades y prestaciones de una única CPU.

También cabe esperar, y por que no, la pronta aparición de otras tecnologías que compitan con CISC y RISC.

Autor: Jordi Palet

Publicado por: Unix Systems (Abril 1995)

Atrás Pág. Principal Arriba Adelante