Hace ya un tiempo que digo que voy a hacer una serie de hilos explicando un poco las entrañas de la consola, o como mínimo, lo que conocemos de ella. Dado que su arquitectura es a grandes trechos (muy grandes, enormes casi) comparable a la de Xbox 360 voy a establecer una comparación directa con esa consola para así tener una idea de qué esperar o no de ella.
Empiezo hoy por la parte con menos misterios de todos, la CPU de la consola.
Cosas que sabemos en general:
1. Herencia directa de la CPU de Wii/GC.
1.1 Ejecución de instrucción fuera de orden limitada
1.2 1 hilo de ejecución por núcleo
1.3 Pipeline corta (4 y 7 estapas para enteros/coma flotante)
2. 3 núcleos.
3. eDram en vez de SRAM para las cache L2. Como puede emular la Wii por hardware, sabemos que a nivel de latencias eso no ha supuesto ningún impacto negativo.
4. Caches L2 incrementadas de 256KB a 2MB/512KB dependiendo del núcleo.
5. Frecuencia a 1,243Ghz en vez de a 0,729Ghz como en la Wii
Diferencias no confirmadas
Parece ser que el análisis del núcleo que se ha hecho en foros ingleses a partir de una imagen proporcionada por una empresa dedicada al escaneo de chips hace sospechar que el número de registros se ha visto incrementado con respecto al núcleo de la Wii. Como eso no podrá saberse del cierto hasta que se filtren imágenes de más resolución o documentación, de momento lo dejamos al aire.
Personalmente tengo otra teoría de otras cosas que podrían haber “cambiado”, que comentaré cuando llegue el momento.
Claves en la comparación con el Xenon Xbox 360:
1. OOE vs IOE (re-ordenación de instrucciones vs no re-ordenación)
2. Pipelines mucho más cortas (4 y 7 etapas para entero/coma flotante vs 23 y 29 en el Xenon) lo que significa que las instrucciones se completan en menos ciclos.
3. Caches (en Xbox 360, 32+32KB L1 por núcleo y 1MB L2 a compartir entre núcleos).
4. Registros de memoria (+ numerosos en Xbox 360).
Empecemos a analizar un poco en profundidad.
Del diseño de la CPU de GC/Wii/WiiU hay que señalar que está basado en la eficiencia, es decir, se prioriza absolutamente las instrucciones ejecutadas por ciclo de reloj. Las ventajas de hacerlo así son muy notables de cara a abaratar el coste de programación.
Con la N64, que era una consola muy complicada debido a ciertos cuellos de botella y otras peculiaridades del hardware, Nintendo vio que lo más caro de una consola podría no ser la consola en sí, sino lo que luego le costaba a la compañía programar los juegos para ella.
Hasta la llegada del 3D los juegos eran muy sencillos, programados por poca gente, así que la diferencia entre una arquitectura fácil de dominar de una complicada no se notaba mucho. A medida que la complejidad de lo que se quería hacer aumentaba el impacto de tener una arquitectura amigable se iba haciendo cada vez más grande, y Nintendo dieron el salto con la GC en ese sentido.
En contraposición con las CPU anteriores, a falta de tener más detalles específicos cuyo impacto no podemos analizar, podemos destacar una cosa del Espresso: el tamaño de la cache L2 es sin duda un salto exponencial.
De GC a Wii los saltos habían sido muy moderados, casi inexistentes. Una de las pocas cosas que se cambiaron fue la versatilidad de la cache L2, 2 way-set associative en GC y 8 way set-associative en la Wii (no vale la pena entrar a explicarlo en profundidad, pero digamos que 8 way set associative es más eficiente en lo que al aprovechamiento de memoria se refiere).
De Wii a WiiU ese apartado ha dado un salto absolutamente bestial. Se pasa a 2MB y 512KB dependiendo del núcleos, lo que significa que más datos podrán ser gestionados sin tener que esperar a la lenta y lejana memoria principal. Además, como la “asociatividad” debe mantenerse PROPORCIONALMENTE en el caso de los núcleos “pequeños” (los de 512KB de cache L2, uno de los cuales se usa para emular la Wii y el otro que por razones obvias de rendimiento deberá ser idéntico), los núcleos pequeños deberían ser por lógica 16 way-set associative, ya que se debe lograr cuando se emula la Wii que los 256KB de cache L2 que se usan para hacerlo sean 8-way set associative.
Supongo que el chip grande también será 16-way set associative por temas de complejidad (a más asociatividad más complejidad en la circuiteria), sencillamente cada “set” será más grande y tendrá más bloques.
La comparación con el Xenon es más complicada ya que son CPUs que difieren en absolutamente todo lo que una CPU puede diferir excepto en el número de núcleos así que el análisis tendrá que ser mucho más extenso.
Tamaño del pipeline
La función de una CPU no es otra que la de ejecutar unas instrucciones, y la cantidad de pasos que realiza para lograrlo es el tamaño del pipeline.
Una CPU de una sola etapa lo haría todo de golpe, y tras terminar una instrucción se pondría a por la siguiente.
Para aumentar la eficiencia de las CPUs se dividió ese proceso en varias etapas menores. Como cada etapa era más sencilla, se podía aumentar la frecuencia. La explicación a grandes trechos es que dividiendo un circuito muy complejo en varios circuitos menos complejos entrelazados entre si, al ser esos circuitos más sencillos y la operación a realizar más pequeña, se completan antes y pueden volver a completarse de nuevo (ciclo) en menor intervalo de tiempo.
La gracia del invento es que como todas las instrucciones pasan por esas etapas de forma lineal, y cada etapa se completa por si misma, una vez una operación ha llegado a una etapa la anterior puede empezar a procesar la siguiente instrucción, para así traducir el incremento en frecuencia en incremento en instrucciones ejecutadas.
Así pues, si a más etapas más frecuencia, y si al final podemos seguir escupiendo una instrucción por ciclo, lo lógico sería hacer infinidad de etapas infinitamente pequeñas, no? Pues no.
Primero porque el tener muchas etapas significa que una instrucción no se completa hasta que ha pasado por todas. Si tenemos 10 etapas, significa que tendremos en el mejor de los casos (porque nos interesa que a cada ciclo se completen tantas instrucciones como sea posible) 10 instrucciones ejecutándose simultáneamente en vez de una sola, con lo que necesitaremos tener a mano un número mayor de datos y por tanto más memoria de bajo nivel para poder funcionar.
Otro impacto evidente es que en caso que haya que vaciar el pipeline por el motivo que sea, se pierden tantos ciclos como etapas a vaciar hay. Así pues, vaciar el pipeline en el Espresso es 4-6 veces más barato que hacerlo en el Xenon.
Y cuando hay que vaciar el pipeline? Bueno, el pipeline se vacía en los siguientes casos:
La operación B depende del resultado de la operación A y no hay nada más que hacer de mientras. Como B no puede empezar hasta que A termina, las etapas de la operación B no pueden completarse hasta el fin de A. Existen otros tipos de dependencia entre instrucciones que también pueden derivar en la necesidad de vaciar el pipeline, pero tampoco hace falta explicarlos uno por uno.
Error en la predicción de brancas. Cuando una CPU se encuentra una branca (un “if” en programación de alto nivel para que nos hagamos a la idea) para no quedarse sin hacer nada especula con qué resultado será el más probable y empieza a resolverlo de antemano. Si luego resulta que ha especulado mal debe vaciar el pipeline para resolver las operaciones buenas.
Adicionalmente, la predicción de brancas requiere de un tamaño muy superior en caso que la longitud de la Pipeline sea muy superior. El motivo es sencillo, un BP (branch predictor) es sencillamente una unidad encargada de resolver brancas (ifs) y que mientras las resuelve ejecutan una predicción basada en unos parámetros, y para optimizar empiezan a ejecutar instrucciones en base a esa predicción. Esas instrucciones aún cuando se ejecutan no pueden darse por finalizadas, y por tanto deben guardarse en un registro de instrucciones a parte, para luego en caso de confirmarse la predicción darse por bueno y copiarse donde toca, o en caso de predicción fallida toca vaciar pipeline (deshacer lo hecho) y volver a llenar-la con el código correcto.
En caso de que la branca no se haya resuelto cuando se ha llenado ese registro de la unidad de predicción de brancas se paraliza la ejecución de instrucciones hasta que se resuelva.
Todo eso tiene un impacto superior en pipelines largas, que unido al hecho de que hay otros componentes físicos (resistencia a la electricidad, comportamientos de la energía a altas frecuencias, etc.) que impiden que un procesador de silicio incremente su frecuencia indefinidamente por el simple hecho de simplificar sus estructuras, hacen que una pipeline larga sea cada vez menos atractiva (siguen habiéndolas y siguen teniendo sus ventajas también, por ejemplo, las del Jaguar de PS4/Xbox One son también pipelines muy largas).
Ejecución en orden vs Ejecución fuera de orden
Un programa moderno es un conjunto de "sub-programas" que realizan tareas menores, y esos "sub-programas" están formados por instrucciones que se mandan a la CPU, y que con unos datos que también se le mandan, se ejecuta una funcionalidad determinada.
Por ejemplo, 1 + 3 sería un programa con dos datos y una instrucción (que internamente se traduce en más operaciones, ya que hay que escribir esos datos en los registros para luego ejecutar la operación y volver a escribir el resultado final).
Hay una condición que debe cumplirse, el resultado de las instrucciones tiene que ser el esperado, y por consiguiente, al final la cosa ha de quedar ordenada.
Pero qué sucedería si pudiésemos desordenar el código internamente, para luego ordenarlo otra vez y así aprovechar mejor la CPU?
Una CPU en orden funciona siempre así:
Coge instrucción.
Ejecuta instrucción.
Finaliza instrucción.
Una CPU fuera de orden, sin embargo, funciona así:
Coge instrucción/es
Ordena instrucciones (cola de instrucciones)
Ejecuta instrucciones en orden de cola
Guarda el resultado desordenado
Finaliza instrucción en orden
Veamos qué posibilidades conlleva eso:
Cada CPU cuenta con múltiples unidades de ejecución. Que nos interesen, en el Espresso tenemos, por cada núcleo:
IU0 4 etapas
IU1 4 etapas
FPU 7 etapas
BP (branch prediction unit, funciona distinto a las demás y no entraré en detalle, pero afecta debido al tema OOE vs IOE)
Adicionalmente están las unidades relacionadas con la memoria (Load/Store Unit y System Register Unit) para cargar/guardar datos desde/a la caché/registros.
El Xenon, por su parte, tiene:
IU 23 etapas
FPU 29 etapas
VMX128 (esta en realidad son 3 unidades distintas, pero no hace falta entrar en detalle porque no influye a nivel de lo que analizamos).
BP
Y también como el Espresso las unidades “secundarias” mencionadas ( LSU, SRU).
Bien, el caso es que el diseño In Order del Xenon es muy fácil de exlicar. Cada hilo de ejecución puede coger 1 instrucción, la manda a la unidad correspondiente (IU, FPU o VMX128) y en caso de situación idílica saldrá otra instrucción finalizada de la unidad en cuestión.
En el caso de WiiU, y basando ese análisis en lo que hacía el Gekko de GameCube (con lo que podría ser que hubiese alguna mejora no confirmada /filtrada que se me escape en ese análisis), cada núcleo hace lo siguiente por thread (aunque en ese caso no hay multi-threading y por consiguiente, sólo se ejecuta un thread concreto por núcleo):
Hasta 4 instrucciones se cogen de la cache L1 de instrucciones por cada ciclo. Esas se van para la cola de reordenamiento.
Desde allí, 2 instrucciones (+1 adicional en caso de que una sea para el BP) se pasan a las unidades correspondientes. En caso óptimo de código, se despacharán 2 (+1) instrucciones por ciclo.
Os habréis fijado que hay 2 unidades para enteros, IU1 e IU2. Ambas son idénticas excepto porque una puede hacer multiplicaciones y divisiones mientras que la otra no. Esa duplicidad existe porque en caso que la IU1 esté ocupada en una operación que dure múltiples ciclos (la instrucción está en una parte del pipeline y necesita permanecer allí durante varios ciclos) se puede mandar otras instrucciones a la IU2 para que vayan resolviéndose allí, para posteriormente reordenar.
En un diseño IOE esas duplicidades no tienen sentido, pues jamás puede una instrucción completarse sin que la anterior se haya completado también.
Multi-threading y caches
Tal y como he dicho antes, un programa complejo no deja de ser un conjunto de sub-programas más simples que hacen una función determinada. Cuando esos "sub-programas" no dependen el uno del otro pueden ejecutarse simultáneamente, y en caso de que la CPU lo soporte, así sucede.
En nuestro caso, el Xenon de Xbox 360 soporta multi-threading mientras que el Espresso no. Las ventajas del multi-threading se notan más en una arquitectura IOE que en una OOE, pues en las OOE la reordenación de instrucciones ya permite un aprovechamiento muy superior de los recursos, y la pipeline se llena más fácilmente. En el caso del Espresso, además, la pipeline es cortísima para tener máxima eficiencia con respecto a los teóricos, con lo que el multi-threading no sería ni mucho menos una opción inteligente.
Aún así, para el Xenon si supone una gran ventaja el tenerlo con respecto el no tenerlo, pero a diferencia del OOE que una vez implementado (con sentido, por supuesto) tiene unos beneficios “transparentes”, el multi-threading tiene sus inconvenientes.
Como los threads son “sub-programas” independientes, no pueden implementarse en todos los escenarios con la misma fuerza. La Xbox360 carece de muchos chips dedicados que si tiene la WiiU, así que en ese sentido lo tiene más fácil para llenar hilos.
El caso es que la naturaleza del multi-threading obliga a tener duplicidad de recursos en lo que a memoria interna de la CPU se refiere. A nivel interno la Xbox 360 tiene los registros de memoria duplicados para el multi-threading, pero cuando llegamos a las cache, aquí aparecen las primeras “penalizaciones” de ese sistema.
Tanto el Xenon como el Espresso tienen 32KB de cache L1 tanto para instrucciones como para datos. Pero si queremos un escenario óptimo de multi-threading para el Xenon, esos 32+32KB de cache L1 deben dividirse entre los threads. Ello aumentará las veces que no encontremos datos/instrucciones en la cache L1 y tengamos que irnos hasta la cache L2 a buscarlos, y aquí llegamos a otra de las grandes diferencias entre ambos procesadores.
El Xenon tiene 1MB de cache L2 a repartir entre 6 threads, suponiendo threads idénticos, eso se traduce en 166KB de cache L2 por cada thread. Los programadores pueden evidentemente programar los threads para que tengan más peso en función de las necesidades, pero el caso es que ese 1MB de cache L2 debe repartirse entre todos los threads.
En comparación, la WiiU implementa un sistema distinto. Cada núcleo tiene su cache dedicada, y esa se reparte en una configuración de 512KB para el núcleo 0, 2MB para el núcleo 1 y 512KB para el núcleo 2.
Si recordáis, antes he dicho que a una pipeline larga hace falta más cache porque se están manejando más instrucciones (y sus correspondientes datos) simultáneamente para aprovechar cada ciclo de reloj, así que la diferencia a nivel de caches es abismal.
Adicionalmente, la versatilidad de las caches del Espresso es superior. Las caches L1 del Xenon son 2-way set associative en el caso de la de instrucciones, y 4-way set associative en el caso de la de datos. Las del Espresso son 8-way set associative tanto para datos como para instrucciones.
En el caso de la cache L2 la diferencia es aún más grande, 2-way set associative en Xbox360 frente a 16-way set associative del Espresso. Proporcionalmente y por núcleo, incluso el más grande de la WiiU es mucho más versátil (doble de tamaño y 8 veces más de associatividad) que el MB que tiene el Xenon de L2.
Las ventajas de la associatividad no son proporcionales, es decir, se nota mucho más la diferencia entre 2-way set associative y 4-way set associative que entre 4-way set associative y 8-way set associative, pero aún así es bueno tenerlo en cuenta.
En resumen, la CPU de WiiU depende muchísimo menos de la RAM principal (tanto en latencias como en ancho de banda) que la de Xbox 360.
Repertorio (Set) de instrucciones
En ese caso el de Xbox 360 es más completo. Gracias a la unidad VMX128 se pueden hacer un determinado número de operaciones relacionadas con los gráficos que en el Espresso no pueden llevarse a cabo, como mínimo, tan directamente. No es que los núcleos del Espresso estén totalmente mancos en ese sentido (de hecho en operaciones de la FPU tiene un rendimiento superior por ciclo a diseños chips como el Bobcat de AMD), pero sí que están cojos en comparación con los núcleos del Xenon.
Eso se traduce en que lo que en el Xenon puede ser una sola instrucción que se manda a la VMX128 en el Espresso el equivalente podrían ser 2 o más instrucciones que se mandan a la FPU.
Las operaciones de la unidad VMX128, eso sí, tienen una latencia de entre 4 y 14 ciclos (son instrucciones más complejas y su ejecución es más lenta, pero aún así, mejor eso que intentar hacer lo mismo con una FPU menos capaz).
Además, esa unidad VMX128 dispone de muchos más registros de los que normalmente se encontrarían en unidades equivalentes (128 por thread) con lo que se tendrá que acceder a la cache L1 con menos frecuencia y acelerará de forma considerable el rendimiento para ese tipo de código.
Chips adicionales
Otro punto para añadir en la comparación, y que en el caso de la WiiU no está relacionado con el tema de la CPU en sí pero que sí influye en la comparación es el de los chips dedicados, sobretodo el DSP.
Un DSP es un chip especializado en funciones de tratamiento del sonido (reverberación y cosas por el estilo), con esas funciones codificadas en hardware y unas pequeñas memorias locales propias para llevarlas a cabo.
Xbox 360 carece de ello, y por consiguiente, debe dedicar recursos de la CPU a hacer ese tipo de cosas. Puede parecer que eso del sonido no debe ser muy importante, pero para que os hagáis a la idea, en Xbox360 se dedica un hilo mínimo para hacer ese tipo de operaciones, y en algunos casos un núcleo entero.
Incluso en el caso de los juegos que ocupan “sólo” uno de los 6 hilos, debido a que el ser humano no tolera bien las distorsiones del sonido (se nota mucho más una distorsión en el sonido que por ejemplo un poco de tearing) el hilo que se dedica al sonido debe tener prioridad frente al otro, con lo que aún sin consumir el núcleo entero sí que pierdes una parte importante de él.
Otros chips que se encuentran en WiiU y no en Xbox 360 son los de compresión para el tabletomando (con lo que aquí no se gana, pero se evita perder ) o el ARM que en WiiU se encarga de procesar las funciones de seguridad del SO y que en 360 se las come la CPU principal aunque con una penalización según Microsoft de entre el 1 y el 3% como mucho. Aún así, ahí está.
Finalmente, el último aspecto que me parece digno de mencionar (y muy determinante también) es la diferencia de latencias con la RAM principal, pero eso lo explicaré en otro hilo en donde me centraré en explicar la arquitectura de memoria de la WiiU.
Conclusiones finales
La CPU de WiiU de coja no tiene nada, pero su uso es distinto al de la Xbox 360. El diseño ha sido heredado en parte de Wii y GC (ése era ya un diseño MUY ROBUSTO y eficiente) con innovaciones (caches eDram que permiten un tamaño brutal) y otros ajustes muy importantes como el tener 3 núcleos en vez de uno.
Su eficiencia le permitirá crujir a la CPU de Xbox 360 en código de propósito general. Pensad como comparación que un programa pensado sin multi-threading rendía en Xbox 360 en el mejor de los casos un 20% mejor que en la Wii.
Eso significa que ese programa usaba sólo un thread, con lo cual 2 de los 3 núcleos de la Xbox 360 estaban sin usar y del otro sólo aprovechaba uno de los dos hilos disponibles, pero también hay que señalar que ese thread tenía para si los 32+32KB de caché L1 (como en Wii o un núcleo del Espresso) y el 1MB de caché L2 (en donde la Wii se tenía que contentar con 256KB).
A partir de ahí la comparación con el Espresso para ese tipo de código es, incluso en el peor de los casos (suponiendo ese 20% de rendimiento superior como algo constante y no como el pico que realmente era), muy favorable a la CPU de WiiU.
No sólo por tener una frecuencia un 70% superior a la Wii, también porque en programas más complejos y con más hilos la disponibilidad de caches en Xbox 360 irá cayendo (sobretodo la L2), mientras que en WiiU cada núcleo tiene su porción para si misma.
Así pues, en un caso de 3 threads (1 por núcleo) la disponibilidad de recursos por núcleo en el caso de la L2 pasaría a ser de 333KB en Xbox 360, frente a los 512KB/2MB de la WiiU. Y la comparativa se vuelve más peliaguda aún porque el 20% anteriormente citado era en un escenario en donde el thread de Xbox 360 tenía disponible 4 veces la cantidad (un 400%) de cache L2 que tenía el procesador de Wii, mientras que ahora pasa a tener tan sólo 1/3 (un 33%) de la capacidad con la que cuenta el Espresso. Si a eso añadimos la brutal latencia con respecto a la RAM principal que hay en Xbox 360 la diferencia no puede hacer más que crecer y crecer en favor del Espresso.
En lo que respecta a código de gráficos, ese código es más fácil de paralelizar y más predecible (más threads y menos necesidad de hacer predicciones de branca que en el caso de Xbox 360 penalizan mucho más que en el de Wii/WiiU) con lo que el diseño de Xbox 360 tendría un rendimiento comparativamente muy superior al que tenía con el código de propósito general y se aproximaría más a sus cifras teóricas.
Por supuesto, el hecho de que el Xenon tenga que hacer tareas que el Espresso tiene relegadas a otros chips dedicados también servirá para aumentar/limar (propósito general/código de gráficos) esas diferencias de forma considerable, con lo que el diseño es en mi opinión, más que suficiente para disfrutar de unos juegos con un rendimiento aceptable.
Bueno, hasta aquí llega la primera parte (y seguramente la más extensa) de esa serie de 4 que voy a hacer para explicar desde mis modestos conocimientos lo que puede uno esperarse de WiiU, y compararlo con Xbox 360 para tener una referencia clara de lo que podemos esperar de ella.
PD: Como el tochaco ese lo he escrito a pedazos en distintos días seguro que habrá fallos en la redacción que ya iré editando a medida que los vea/me los señaléis.