Hardware
Los distintos proveedores de hardware independientes (IHV) definen las topologías de CPU heterogéneas de forma diferente. Intel y Qualcomm usan los términos Performance (rendimiento) y Efficiency (eficiencia), mientras que AMD usa los términos Performance y Compact. Cada proveedor hace concesiones entre rendimiento y eficiencia al diseñar su topología de núcleos. Los ejemplos siguientes ilustran algunas de las decisiones de diseño implicadas:- Diferentes tamaños de caché
- Los diferentes núcleos pueden tener diferentes tamaños de caché.
- Por ejemplo, los núcleos de mayor rendimiento pueden tener cachés L1 y L2 más grandes.
- Uso compartido de la caché
- Puede haber diversos niveles de uso compartido de las cachés entre núcleos.
- Por ejemplo, los núcleos de mayor rendimiento pueden tener cada uno una caché L2 privada, mientras que los núcleos más eficientes pueden compartir una única caché L2.
- Compatibilidad con multithreading simétrico (SMT)
- Algunos núcleos pueden admitir SMT mientras que otros no.
- Intel denomina al SMT Hyper-Threading.
- Diseño interno del núcleo
- Los recursos internos de los distintos tipos de núcleo pueden ser diferentes.
- Por ejemplo, los núcleos de mayor rendimiento pueden incluir más canalizaciones de punto flotante, más canalizaciones de ALU o rutas de datos más anchas.
- Frecuencia máxima
- La frecuencia máxima entre los tipos de núcleo puede ser diferente.
- Por ejemplo, los núcleos de mayor rendimiento pueden alcanzar una frecuencia más alta que los núcleos eficientes.
Cifras de rendimiento
Es de esperar que los núcleos con distintos niveles de eficiencia tengan un rendimiento diferente. Un núcleo está diseñado para el rendimiento y el otro para el ahorro de energía. La cuestión es cuán diferentes son los núcleos. Los datos mostrados son una combinación de varios IHV. Cada IHV tiene un escalado diferente para cada prueba en función de las decisiones que tomó para su topología. Aunque las cifras exactas son diferentes, los patrones son similares en todos los IHV. Además, cada generación de CPU hace concesiones diferentes, lo que ajusta sus cifras relativas. Generar cifras para todos los IHV y generaciones existentes queda fuera del ámbito de este artículo.Pruebas sintéticas
Las matemáticas vectoriales son una parte importante del trabajo que un título de juego realiza dentro de un fotograma. Esta carga de trabajo puede ser cualquier cosa, desde física hasta IA o representación. El juego realiza este trabajo mediante tipos vectoriales con tres o cuatro valores float que caben en un único registro SSE de 128 bits. Abarcan una amplia variedad de operaciones, como productos escalares, multiplicación de matrices, cálculos de raíz cuadrada y transformaciones de vectores. Las diferencias de rendimiento en estas operaciones pueden tener un efecto significativo en el tiempo total del fotograma. Los gráficos siguientes muestran el costo relativo de realizar siete operaciones matemáticas comunes. Las mediciones usan un vector de cuatro valores de punto flotante de 32 bits almacenados en un único registro SSE de 128 bits. Una matriz se compone de cuatro vectores, para un total de dieciséis valores de punto flotante de 32 bits en cuatro registros SSE de 128 bits. Estas operaciones se seleccionaron porque se usan habitualmente dentro de un fotograma.- Producto escalar: calcular el producto escalar de un vector.
- Transformación de vector: transformar un vector multiplicándolo por una matriz.
- Suma de vectores: sumar dos vectores.
- Producto vectorial: calcular el producto vectorial de dos vectores.
- Seno vectorial: calcular una aproximación del seno de cada elemento de un vector.
- FMA vectorial (multiplicación-suma fusionada): multiplicar dos vectores y sumar un tercero.
- Raíz cuadrada inversa: calcular la raíz cuadrada inversa de la longitud de un vector.

Mundo real
Las cifras anteriores muestran pruebas comparativas específicas de forma aislada y solo cuentan parte de la historia. Las cifras siguientes proceden de pruebas comparativas de un motor de juego AAA que usa dos subprocesos principales (simulación y representación) con un conjunto de subprocesos de trabajo. La prueba comparativa usa tres configuraciones de prueba.- Los dos subprocesos principales se ejecutan solo en los núcleos de mayor rendimiento.
- Los dos subprocesos principales se ejecutan solo en los núcleos eficientes.
- Los dos subprocesos principales se ejecutan en todos los núcleos del sistema.

Cada fabricante puede ajustar estas heurísticas del programador para adaptarlas a su hardware. Por ejemplo, AMD tiene su Workload Profile Scheduling e Intel tiene su tecnología Thread Director. Cada IHV también tiende a ajustar las heurísticas para que coincidan con la topología específica del SoC.
Recomendaciones
Afinidad de subprocesos
El consejo para la afinidad de subprocesos cuando se ejecuta en un equipo de escritorio con una única clase de eficiencia es evitar establecer afinidades de subproceso rígidas y, en su lugar, dejar que los subprocesos floten por todos los núcleos. La razón principal de esta incertidumbre es que se desconoce de antemano qué otros procesos podrían estar ejecutándose en el equipo de un usuario. Es posible que un subproceso con una afinidad rígida acabe compartiendo un núcleo con otro subproceso de alta prioridad del sistema. El efecto neto es que el subproceso del título se queda sin tiempo de ejecución. Si esta contención se produce en un subproceso crítico para el fotograma, el título puede experimentar tiempos de fotograma incoherentes. Debe asegurarse de que el sistema operativo tenga un lugar al que mover los subprocesos del título para mantenerlos en ejecución. Sin embargo, este consejo debe cambiar cuando se ejecuta en CPU heterogéneas. La razón principal es que el sistema operativo no sabe necesariamente cuáles de los subprocesos o trabajos del título son críticos para el fotograma. Tiene algunas heurísticas para determinar cuáles cree que son los subprocesos críticos para el fotograma, pero esas heurísticas pueden tomar decisiones incorrectas. Podría decidir mover uno de esos subprocesos o trabajos a un núcleo de menor rendimiento y, por tanto, crear una mayor probabilidad de problemas de rendimiento en el título. La recomendación es procurar que los subprocesos del título puedan ejecutarse como mínimo en tres o cuatro núcleos, y es preferible que sean más. Por ejemplo, en un sistema de gama baja que tenga un núcleo de mayor rendimiento y tres núcleos de menor rendimiento, debe permitir que los subprocesos floten por todos los núcleos. En equipos con tres o más núcleos de rendimiento, podría restringir los subprocesos críticos para el fotograma solo a esos núcleos de mayor rendimiento. En equipos de gama alta con muchos núcleos que también admiten SMT, algunos títulos podrían intentar fijar sus subprocesos a núcleos físicos. Esta técnica evita la contención a nivel de núcleo físico cuando varios subprocesos de alta prioridad comparten los mismos recursos de un núcleo físico. En general, sin embargo, no se recomienda considerar este caso. El sistema operativo ya aplica este comportamiento en todo el sistema. Programa los subprocesos primero entre los núcleos físicos y usa más núcleos lógicos solo cuando es necesario. El único caso que un título debería considerar es cuando hay suficientes núcleos de mayor rendimiento en el sistema para admitir los subprocesos críticos del título. En este caso, podría ser deseable fijar los subprocesos críticos para el fotograma a los núcleos de mayor rendimiento. Sin embargo, debe permitirse que esos subprocesos floten por todos los núcleos de mayor rendimiento. Aunque en algunos casos permitir que todos los subprocesos floten por todos los núcleos puede mejorar el rendimiento, este comportamiento no es coherente entre todos los fabricantes de CPU. Si el título implementa un sistema de robo de trabajo (work stealing) que puede equilibrar completamente la carga de trabajo entre todos los subprocesos, debe permitirse que cualquier subproceso de trabajo flote por todos los núcleos. No importa si son subprocesos de trabajo de alta o baja prioridad. El algoritmo de robo de trabajo equilibra automáticamente la carga y permite completar la mayor cantidad de trabajo en el menor tiempo posible. Los subprocesos en los núcleos de mayor rendimiento ejecutan más trabajos durante el fotograma. Si hay varios subprocesos críticos para el fotograma, puede ser útil establecer su afinidad de forma que dos subprocesos críticos para el fotograma no puedan ejecutarse en el mismo núcleo. Por ejemplo, si hay dos subprocesos críticos para el fotograma, establecer su afinidad como una exclusión mutua (or exclusivo) entre ellos elimina la posibilidad de que el sistema operativo haga que los subprocesos compitan entre sí. De nuevo, sin embargo, considere esta opción solo si hay suficientes núcleos para admitir este caso y, al mismo tiempo, permitir que cada subproceso crítico para el fotograma flote por un mínimo de tres o cuatro núcleos.Topología de subprocesos
Al considerar cómo asignar trabajo a los subprocesos en un entorno heterogéneo, los datos principales giran en torno a la duración de las distintas tareas y a cómo interactúan entre sí. Cuál es la duración media de cada tarea, cuáles son las dependencias entre las tareas, cuáles son las prioridades de cada tarea, etc. ¿Cuánto pueden absorber los subprocesos de trabajo los picos de tiempo en cualquier subproceso o trabajo crítico? Si uno de esos subprocesos o trabajos se ejecuta un 35 por ciento más despacio, ¿cuánto afecta al tiempo de fotograma? Por ejemplo, si una tarea se alarga, ¿cuánto afecta este retraso al resto del fotograma? ¿Se detiene el sistema hasta que se completa esa tarea? Puede ser útil determinar cuánto afecta un sistema heterogéneo al tiempo de fotograma agregando sobrecarga adicional en los subprocesos o trabajos críticos. Como prueba, haga que un trabajo o bloque de trabajo tarde aleatoriamente entre un 25 % y un 35 % más de tiempo. El grado de cambio en el tiempo total de fotograma indica lo bien que el sistema de trabajos puede absorber los picos de ejecución. La situación ideal es que los tiempos de fotograma apenas cambien. Significa que el sistema puede absorber picos arbitrarios en el tiempo de fotograma. Los picos provienen de ejecutarse en un núcleo de menor rendimiento o de que otro proceso aleatorio del sistema use tiempo de CPU.Comunicación entre subprocesos
Conviene reflexionar sobre la comunicación entre subprocesos mediante primitivas de bloqueo. La sobrecarga de un cambio de contexto está estrechamente vinculada al estado C del núcleo receptor antes de que el nuevo subproceso comience a ejecutarse. El núcleo debe estar en C0, el estado de ejecución, antes de que el nuevo subproceso pueda comenzar a ejecutarse. El caso más barato es cuando el núcleo ya estaba en ejecución: ya está en C0. Sin embargo, si el núcleo está inactivo, se encuentra en un estado C inferior. El nivel del estado C corresponde a niveles más profundos de suspensión. Una suspensión más profunda hace que el núcleo consuma menos energía. Sin embargo, es más costoso despertarlo y que vuelva a ejecutarse desde una suspensión más profunda. El estado C de bajo consumo más común es C1, que puede agregar una sobrecarga de 2 a 7 μs. El siguiente estado C, C2, puede agregar una sobrecarga de aproximadamente 40 a 100 μs. La cifra exacta es específica de cada IHV y SoC. Hay muchas variables que controlan en qué estado C se encuentra un núcleo cuando está inactivo; es un equilibrio entre capacidad de respuesta y consumo de energía. Un estado C más bajo consume menos energía que un estado C más alto. Es común que un núcleo entre primero en C1 y luego cambie a C2 si lleva un tiempo sin ejecutar código. Hay otra preocupación: qué ocurre cuando el subproceso que envía el trabajo y el subproceso que lo ejecuta están en núcleos con un perfil de rendimiento diferente. Técnicamente, es posible que el subproceso de trabajo complete trabajos más rápido de lo que se crean los nuevos. Es más probable que esta situación ocurra si el subproceso que ejecuta el trabajo está en un núcleo más rápido que el subproceso que envía los trabajos. Si un trabajo tarda menos en ejecutarse de lo que tarda en crearse, el título puede llegar a una situación en la que un subproceso de trabajo se suspende constantemente esperando nuevos trabajos. El efecto neto es que los trabajos se vuelven más costosos de ejecutar porque el subproceso de trabajo se suspende constantemente. El costo de la sobrecarga de un cambio de contexto se suma al tiempo de ejecución de cada trabajo. Esta sobrecarga puede, paradójicamente, provocar velocidades de fotogramas más bajas al ejecutarse en núcleos de mayor rendimiento. Por ejemplo, un trabajo que solía tardar 250 μs puede empezar a tardar de repente 275 μs; esta regresión es una caída del 10 % en el rendimiento. Debido a este caso, la recomendación es que los trabajos se envíen por lotes en lugar de a medida que se crean. Por ejemplo, si el sistema sigue un patrón en el que crea un trabajo, envía el trabajo, crea un trabajo, envía un trabajo, etc. Este patrón debe ajustarse para, en su lugar, realizar primero un lote de creaciones y luego enviarlas todas al mismo tiempo. Si el título usa un objeto que requiere WaitForSingleObject, debe colocarse dentro de un pequeño bucle de espera activa (spin). Llame a la función con un tiempo de espera de 0 y, una vez terminado el spin, use el valor de tiempo de espera normal del título. Sin embargo, la mejor solución para este problema es usar API de sincronización modernas, como los bloqueos Slim Reader/Writer (SRW), las secciones críticas y WaitOnAddress. Todas estas API de sincronización incluyen internamente algún tipo de spin, que puede evitar que un subproceso se suspenda constantemente. Este spin es especialmente útil para los bloqueos que se mantienen durante poco tiempo. Es importante evitar los bloqueos de espera activa largos. Por ejemplo, tener un patrón en el que los subprocesos giran constantemente esperando trabajo que realizar. Si hay otro trabajo que debe realizarse en el núcleo, el programador acabará suspendiendo el subproceso en espera activa para realizar ese trabajo. Cuando se produce esta interrupción, hay una alta probabilidad de que el subproceso suspendido permanezca suspendido durante una cantidad de tiempo significativa. Todo este comportamiento se realiza para evitar la inanición de otros subprocesos. Dado que el título tiene poco control sobre cuándo se produce la suspensión, esto puede provocar tirones en la velocidad de fotogramas.Sistemas de trabajos
Para obtener el mejor rendimiento, estructure el motor en torno a trabajos en lugar de depender de subprocesos críticos para el fotograma dedicados y de larga duración. Por ejemplo, un modelo de subprocesos de simulación/representación crea dos subprocesos críticos para el fotograma persistentes. Sin embargo, los trabajos largos pueden hacer que cualquier subproceso de trabajo se comporte como un subproceso crítico para el fotograma. Si un trabajo tarda 10 ms, el subproceso que lo ejecuta se vuelve crítico para el fotograma durante ese período, independientemente de su función original.Robo de trabajo
La recomendación número uno es usar el robo de trabajo en todo el sistema de trabajos y dejar que cualquier subproceso crítico para el fotograma participe en el algoritmo de robo de trabajo. Por ejemplo, si el subproceso de representación está esperando a que se completen trabajos relacionados con la representación, debería ejecutar algunos de esos trabajos. Este comportamiento ya es una práctica común en muchos motores de juego. Sin embargo, vale la pena mencionarlo aquí porque este tipo de comportamiento es fundamental para que el motor del juego pueda absorber picos de fotogramas debidos a los núcleos heterogéneos y a la posible interrupción por parte del sistema operativo. Cuando un trabajo tarda más en ejecutarse, aumenta el riesgo de un pico en el tiempo de fotograma. Este comportamiento puede deberse a que el trabajo se ejecute en un núcleo de menor rendimiento o a otro trabajo dentro del equipo. La recomendación es que el motor procure que un trabajo dure aproximadamente el dos por ciento del fotograma. Este presupuesto significa, por ejemplo, no más de unos 333 μs para un juego que se ejecuta a 60 fps. Si es necesario, varios trabajos más pequeños deben agruparse en un trabajo más grande para acercarse a esa cifra del dos por ciento. Este enfoque proporciona un buen equilibrio entre el tamaño de los trabajos, la comunicación entre subprocesos y la capacidad de absorber picos. Estos ejemplos muestran los efectos que los trabajos largos críticos para el fotograma pueden tener en el rendimiento del sistema. En estos ejemplos, cada trabajo consta del mismo número de operaciones. En este ejemplo, un único trabajo tarda 375 μs en un núcleo lento y 250 μs en un núcleo rápido. El trabajo que actúa como cuello de botella (long pole) es 6 veces más costoso que los demás trabajos. La peor situación es cuando el trabajo largo acaba en uno de los núcleos lentos. Esto puede tener un efecto drástico en los tiempos de fotograma. En este escenario, el tiempo total para ejecutar el trabajo es de 2250 μs, seis veces 375 μs. Figura 3: Ejemplo de trabajo largo ejecutándose en un núcleo más lento.



