Skip to main content

Resumen

Partiendo de las bases establecidas en XBOX One frente a XBOX Series X|S, esta entrada aborda los problemas de rendimiento y los costos asociados a las diferentes operaciones de lectura/escritura en memoria compartida entre núcleos del procesador. Para repasar la arquitectura, las características y el rendimiento de las CPU de las consolas de la familia XBOX One y XBOX Series, le sugerimos que eche un vistazo a la entrada mencionada. Introducción Datos compartidos Los costos de compartir datos Recomendaciones Apéndice: código

Introducción


XBOX One frente a XBOX Series X|S describe el costo asociado a un error de caché L1. Uno de los patrones más comunes de un error de caché L1 es escribir en una dirección compartida entre núcleos de CPU; por ejemplo, datos compartidos entre varios subprocesos. El propósito de esta entrada es proporcionar números relativos de la penalización asociada a compartir datos con capacidad de escritura entre núcleos. La principal conclusión de nuestras pruebas es que es preferible limitar los datos de las operaciones de lectura/escritura a cada núcleo individual, en lugar de compartir datos entre núcleos y subprocesos. Compartir datos entre núcleos ralentizará el procesamiento de su código en lugar de acelerarlo. La familia XBOX One usa un procesador Jaguar y las consolas XBOX Series usan un procesador Hercules. Ambos procesadores usan el protocolo MOESI - Wikipedia para mantener un modelo de memoria fuerte y la coherencia de caché. Cada línea de caché puede estar en uno de cinco estados.
  • Modified
    • Este procesador tiene la única copia válida de la línea de caché y ha realizado cambios.
    • La línea de caché no coincide con la memoria.
  • Owned
    • Este procesador tiene la única copia válida y ha realizado cambios.
    • Otros procesadores pueden tener una copia de solo lectura.
  • Exclusive
    • El procesador tiene la única copia de la línea de caché.
    • El contenido de la línea de caché coincide con la memoria.
  • Shared
    • Este procesador tiene una de varias copias de la línea de caché.
    • Otro procesador puede tenerla en el estado Owned si se han realizado cambios.
  • Invalid
    • Esta línea de caché no es válida y debe capturarse antes de acceder a ella.
Consulte el AMD64 Architecture Programmer’s Manual Volume 2: System Programming, que se encuentra en AMD Developer Central, para obtener información más detallada sobre la implementación de AMD.

Datos compartidos


El uso compartido de datos puede producirse de varias maneras. Una de las más obvias es el uso compartido directo de variables, como un recuento de referencias en una estructura. Esto puede dar lugar fácilmente a que los datos se modifiquen a medida que cada subproceso hace copias a través de un shared_ptr, por ejemplo, como parámetro en una llamada a función. Esa operación por sí sola producirá contención de recursos. Otro tipo de uso compartido de datos, conocido como uso compartido falso (false sharing), es más difícil de detectar. El uso compartido falso ocurre cuando dos direcciones de memoria se resuelven en la misma línea de caché. La línea de caché L1 de los procesadores Jaguar y Hercules tiene un tamaño de 64 bytes y está alineada a 64 bytes, por lo que dos enteros en el mismo bloque de 64 bytes se consideran compartidos. Considere, por ejemplo, las dos estructuras de datos siguientes:
Los datos threadOne, threadTwo, threadThree y threadFour son únicos por subproceso. El subproceso uno solo operará sobre los datos threadOne, el subproceso dos solo sobre los datos threadTwo, y así sucesivamente. Entre estas dos estructuras de datos, OuterClassSlow podría operar hasta veinte veces más lento que OuterClassFast al cambiar al procesamiento multiproceso solo con una operación de escritura estándar. Esto se debe a que todos los datos de OuterClassSlow comparten la misma línea de caché como resultado del uso compartido falso. Están adyacentes entre sí en la memoria. Este es un ejemplo de la diferencia entre una matriz de estructuras (AoS) y una estructura de matrices (SoA).

Los costos de compartir datos


Perfil de prueba

El perfil de pruebas siguió estos criterios:
  • Las instrucciones probadas fueron una lectura sin procesar, una escritura sin procesar, una carga atómica, un almacenamiento atómico y una operación atómica de comparación y almacenamiento (CAS). El código usado para cada conjunto de instrucciones se proporciona en el Apéndice: código.
  • Las pruebas se realizaron con una ubicación de memoria de 8 bytes compartida por todos los subprocesos (línea de caché compartida) o con cada subproceso teniendo una ubicación de memoria única de 8 bytes (línea de caché única).
  • Se ejecutaron cuarenta mil operaciones en un bucle cerrado.
  • El optimizador se configuró para minimizar el tamaño con el fin de deshabilitar el desenrollado de bucles.
  • Se realizaron un total de 100 ejecuciones; la mediana de todas las ejecuciones se muestra en las tablas siguientes.

Pruebas en un solo núcleo

Como línea base, comenzamos probando cada operación en un solo núcleo. | Operación | XBOX One | XBOX One X | XBOX Series S - 3.4 GHz | XBOX Series X|S - 3.6 GHz | XBOX Series X - 3.8 GHz | |--------------|----------:|----------:|----------:|----------:|----------:| | Raw Read | 68.78 us | 52.34 us | 11.81 us | 11.16 us | 10.56 us | | Atomic Load | 68.78 us | 52.34 us | 11.81 us | 11.16 us | 10.56 us | | Raw Write | 114.61 us | 104.64 us | 11.81 us | 11.16 us | 10.56 us | | Atomic Store | 369.55 us | 278.99 us | 207.11 us | 195.61 us | 185.31 us | | Atomic CAS | 668.79 us | 509.02 us | 203.43 us | 192.13 us | 182.03 us | Lo primero es la diferencia de tiempos entre el procesador Jaguar y el procesador Hercules. Hay hasta un aumento de cuatro veces en el rendimiento en Hercules. Esto se debe a una combinación de la mayor velocidad de reloj, el mayor ancho de banda y la compatibilidad con más operaciones de memoria simultáneas en curso. Comparando 40 000 lecturas directas con 40 000 cargas atómicas, no hay ninguna diferencia en los tiempos. Esto se debe al modelo de memoria fuerte de la CPU y al protocolo MOESI. El compilador puede generar las mismas instrucciones para ambas operaciones. Las operaciones de escritura sin procesar y de almacenamiento atómico en esta prueba intentan incrementar un valor en memoria. En esta prueba hay un aumento del doble en el costo entre una escritura sin procesar y un almacenamiento atómico. Esto se debe a que el almacenamiento atómico usa la instrucción xchg, que incluye implícitamente el prefijo lock. Esto tiene la posibilidad de detener el procesador hasta que los resultados se hayan vaciado a la caché, porque no puede reordenar operaciones entre instrucciones que usan el prefijo lock. La escritura sin procesar usa la instrucción mov. Esta no detendrá el procesador; se le permite libremente reordenar operaciones a través de la instrucción mov. Esto nos lleva a la operación atómica de comparación y almacenamiento (CAS). La operación de esta prueba es similar a las pruebas de escritura sin procesar y de almacenamiento atómico: intentar incrementar un valor en memoria. En esta prueba se usa el prefijo lock con la instrucción cmpxchg para hacerla comparable a la prueba de almacenamiento atómico. En el procesador Jaguar, la operación CAS es más costosa que la instrucción xchg mencionada anteriormente debido a la comparación. Sin embargo, el procesador Hercules tiene una implementación mejorada que mantiene el costo similar al de un almacenamiento atómico.

Pruebas en varios núcleos

Todas las pruebas de varios núcleos usaron dos subprocesos en varias configuraciones de núcleos.
  • Dos subprocesos en núcleos físicos separados, pero en el mismo clúster.
  • Dos subprocesos en núcleos físicos separados y en clústeres separados.
  • Cuando SMT estaba habilitado, dos subprocesos ejecutándose en los dos núcleos lógicos de un único procesador físico.
Hicimos dos ejecuciones diferentes para cada prueba. En la primera ejecución, los dos subprocesos compartían la misma dirección uint64_t. En la segunda ejecución, cada subproceso tenía su propia dirección uint64_t única que no compartía líneas de caché. La razón por la que hicimos esto fue para que fuera más fácil ver la penalización de rendimiento cuando las líneas de caché se comparten entre procesadores. Para cada prueba, los subprocesos se iniciaron al mismo tiempo, y el tiempo se midió solo a partir de la iteración del bucle. Los subprocesos también se establecieron en prioridad alta para reducir al máximo la posibilidad de un cambio de contexto. Se realizaron cien ejecuciones para cada prueba; los resultados de la mediana se muestran en las tablas siguientes. Los números que se indican a continuación son todos relativos a la prueba base de un solo núcleo: cuánto más costosa fue la operación al ejecutarse en varios núcleos.

Lectura sin procesar/carga atómica

Se trata de una lectura directa desde una ubicación de memoria. No hay contención entre los subprocesos, porque nada está cambiando los datos: cada subproceso tiene su propia copia de los datos en su caché L1. Como resultado, los tiempos entre leer datos compartidos y leer datos únicos son los mismos. Debido al modelo de memoria fuerte, el compilador puede generar el mismo código para una carga atómica que para una lectura normal. Esto significa que los tiempos son los mismos para cada operación. | Prueba | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - sin SMT | |--------------|----------:|----------:|----------:|----------:|----------:| | Single Core | 1.00 | 1.00 | 1.00 | 1.00 | | Same Physical Shared | N/A | N/A | 1.97 | N/A | | Same Cluster Shared | 1.00 | 1.00 | 1.00 | 1.00 | | Cross Cluster Shared | 1.00 | 1.00 | 1.00 | 1.00 | | Same Physical Unique | N/A | N/A | 1.97 | N/A | | Same Cluster Unique | 1.00 | 1.00 | 1.00 | 1.00 | | Cross Cluster Unique | 1.00 | 1.00 | 1.00 | 1.00 | Hay dos conclusiones principales de esta tabla. La primera es que solo leer de la memoria no tiene ningún efecto en el rendimiento, incluso cuando varios núcleos leen todos desde la misma ubicación. Cada procesador tiene una copia válida en su caché y su línea de caché está en el estado Shared, ya que más de un procesador tiene una copia. La segunda es la duplicación del tiempo para la configuración en la que ambos subprocesos se ejecutan en el mismo núcleo físico. Cuando SMT está habilitado, los recursos del núcleo se comparten entre dos subprocesos. Esto puede dar lugar a una ganancia de rendimiento porque, con frecuencia, un subproceso no puede utilizar todos los recursos disponibles. Sin embargo, esta prueba es un bucle muy cerrado que domina la unidad de carga/almacenamiento y la caché L1. Un solo subproceso puede usar todas las ranuras de la unidad de carga/almacenamiento, por lo que los dos subprocesos deben compartirlas en un formato round-robin. El efecto neto es que cada subproceso tarda el doble; sin embargo, el núcleo realiza el doble de trabajo. En general, el núcleo realiza la misma cantidad de trabajo en la misma cantidad de tiempo.

Escritura sin procesar

Una escritura simple en memoria compartida entre núcleos será significativamente más lenta que una escritura de un solo núcleo en una ubicación de memoria no compartida. La operación de escritura hace que el núcleo actualice la línea de caché e invalide cualquier copia en otros núcleos. Esta prueba en particular es un incremento de un valor en memoria compartida. Esto significa que si el núcleo no tiene una copia válida en su caché, necesita solicitar los datos a la memoria o al último escritor de la dirección. Esto puede crear un efecto de ping-pong entre los dos núcleos, en el que un núcleo actualiza los datos, el segundo núcleo lee los datos del primer núcleo y los actualiza, y luego el primer núcleo tiene que leer los datos del otro núcleo antes de poder actualizarlos. Esto continúa de un lado a otro en cada iteración del bucle de prueba. La sobrecarga del efecto ping-pong se puede ver en esta tabla. | Prueba | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - sin SMT | |--------------|----------:|----------:|----------:|----------:|----------:| | Single Core | 1.00 | 1.00 | 1.00 | 1.00 | | Same Physical Shared | N/A | N/A | 10.69 | N/A | | Same Cluster Shared | 10.54 | 8.73 | 24.36 | 23.59 | | Cross Cluster Shared | 13.61 | 12.78 | 18.89 | 23.57 | | Same Physical Unique | N/A | N/A | 1.98 | N/A | | Same Cluster Unique | 1.05 | 0.93 | 1.03 | 1.03 | | Cross Cluster Unique | 1.01 | 0.84 | 1.04 | 1.03 | Hay varias conclusiones de esta tabla. La primera es la diferencia en el impacto relativo en el rendimiento entre los procesadores Jaguar y Hercules. En Hercules, el costo de compartir una línea de caché es más costoso que en Jaguar. En este caso, Jaguar tiene un costo de 17 ciclos para solicitar los datos del otro núcleo; la misma operación en Hercules cuesta 90 ciclos. Esto se debe a la caché más profunda de Hercules, la existencia de una caché L3 que no existe en Jaguar. La segunda es la diferencia entre usar una ubicación de memoria compartida y una ubicación única. En este caso, la operación es un incremento del valor actual; se trata de una operación de lectura/modificación/escritura. La operación de lectura debe ser satisfecha por el núcleo que escribió más recientemente en esa ubicación de memoria. Si no es el mismo núcleo, el costo de la lectura es drásticamente mayor. Como se puede ver en la tabla, esto podría ser hasta veinticinco veces más costoso. La tercera es la diferencia relativa menor cuando los dos subprocesos comparten un único núcleo físico con SMT habilitado. La línea de caché actualizada no necesita capturarse desde otro núcleo; ya es local. Sin embargo, no se puede aplicar el reenvío de almacenamiento a carga (store-to-load forwarding). Cuando el subproceso A actualiza el valor, este debe vaciarse a la caché antes de que el subproceso B pueda usar el valor. La cuarta es la duplicación del tiempo para la configuración en la que ambos subprocesos se ejecutan en el mismo núcleo físico pero operan en memoria única. Cuando SMT está habilitado, los recursos del núcleo se comparten entre dos subprocesos. Esto puede dar lugar a una ganancia de rendimiento porque, con frecuencia, un subproceso no puede utilizar todos los recursos disponibles. Sin embargo, esta prueba es un bucle muy cerrado que domina la unidad de carga/almacenamiento y la caché L1. Un solo subproceso puede usar todas las ranuras de la unidad de carga/almacenamiento, por lo que los dos subprocesos deben compartirlas en un formato round-robin. El efecto neto es que cada subproceso tarda el doble; sin embargo, el núcleo realiza el doble de trabajo. En general, el núcleo realiza la misma cantidad de trabajo en la misma cantidad de tiempo.

Almacenamiento atómico

El almacenamiento atómico usa la instrucción xchg, que señala implícitamente la marca lock. La instrucción xchg debe leer y luego escribir en la dirección de memoria en cuestión. Durante este tiempo, la línea de caché está bloqueada, lo que impide que los demás núcleos accedan a esa línea de caché. Esto puede detener una operación pendiente hasta que la instrucción xchg haya escrito los datos en la caché. Otro costo es que el núcleo no puede reordenar operaciones a través de las instrucciones xchg. El costo final se produce si otro núcleo ha modificado los datos. En ese caso, los datos deberán capturarse desde ese núcleo. Cualquier otro núcleo tendrá que esperar durante toda esta secuencia cuando opere en la línea de caché compartida. Al igual que en la prueba de escritura sin procesar, la prueba de almacenamiento atómico puede tener un efecto de ping-pong similar; sin embargo, en este caso es más pronunciado e incluso afecta a la prueba de un solo núcleo. El procesador no puede ejecutar especulativamente más allá de una iteración del bucle. El subproceso A ejecuta la instrucción xchg mientras el subproceso B espera acceso. En cuanto el subproceso A termina, el subproceso B puede continuar e inmediatamente solicita una copia antes de que el subproceso A pueda solicitar acceso en la siguiente iteración del bucle. Esta tabla muestra el costo relativo de la instrucción xchg en comparación con un solo núcleo realizando la operación. | Prueba | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - sin SMT | |--------------|----------:|----------:|----------:|----------:|----------:| | Single Core | 1.00 | 1.00 | 1.00 | 1.00 | | Same Physical Shared | N/A | N/A | 2.38 | N/A | | Same Cluster Shared | 6.84 | 6.85 | 4.17 | 4.04 | | Cross Cluster Shared | 10.64 | 13.40 | 3.72 | 3.56 | | Same Physical Unique | N/A | N/A | 0.98 | N/A | | Same Cluster Unique | 1.00 | 1.00 | 1.00 | 1.00 | | Cross Cluster Unique | 1.01 | 1.00 | 1.00 | 1.00 | Hay dos conclusiones de los datos de esta tabla. Los tiempos relativos al usar una dirección compartida no son tan extremos para un almacenamiento atómico como para una escritura sin procesar. La razón es que una gran parte del costo de esta prueba ya se ha pagado con la prueba de un solo núcleo. Sin embargo, el costo total del almacenamiento atómico sigue siendo de dos a cuatro veces más caro que una escritura sin procesar en Hercules y hasta trece veces más caro en Jaguar. La segunda es que Hercules tiene una implementación mejorada para las operaciones lock, que en este caso hace que las pruebas dentro del mismo clúster y entre clústeres tarden aproximadamente el mismo tiempo. El procesador Jaguar puede ser hasta el doble de costoso cuando los datos se comparten entre clústeres y se usan operaciones lock.

Comparación y almacenamiento atómicos (CAS)

La operación atómica de comparación y almacenamiento (CAS) de esta prueba usa la instrucción cmpxchg con el prefijo lock, lo que significa que mientras se realiza la operación, la línea de caché está bloqueada y otros procesadores no pueden acceder a ella. Es la misma forma en que funciona la instrucción xchg en la prueba de almacenamiento atómico. En esta prueba, la operación CAS realiza los mismos cálculos que la operación de almacenamiento atómico. Intenta incrementar el valor en memoria, ya sea compartida o única. La única diferencia es que la operación CAS solo escribirá en la ubicación de memoria si es igual a un tercer valor especificado. | Prueba | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - sin SMT | |--------------|----------:|----------:|----------:|----------:|----------:| | Single Core | 1.00 | 1.00 | 1.00 | 1.00 | | Same Physical Shared | N/A | N/A | 2.91 | N/A | | Same Cluster Shared | 4.32 | 4.32 | 4.01 | 4.24 | | Cross Cluster Shared | 10.12 | 14.80 | 3.52 | 3.45 | | Same Physical Unique | N/A | N/A | 0.91 | N/A | | Same Cluster Unique | 1.00 | 1.00 | 1.00 | 1.00 | | Cross Cluster Unique | 1.00 | 1.00 | 1.00 | 1.00 | Dado que tanto la prueba de almacenamiento atómico como la de comparación y almacenamiento atómicos usan el prefijo lock, sus costos relativos son los mismos y por las mismas razones. Los tiempos relativos entre las operaciones de un solo subproceso y de varios subprocesos que usan una dirección compartida no son tan extremos porque una gran parte del costo de esta prueba ya se ha pagado con la prueba de un solo núcleo. Sin embargo, el costo total de la operación CAS sigue siendo de dos a cuatro veces más caro cuando los datos se comparten en Hercules y hasta catorce veces más caro en Jaguar. La segunda es que Hercules tiene una implementación mejorada para las operaciones lock, que en este caso hace que las pruebas dentro del mismo clúster y entre clústeres tarden aproximadamente el mismo tiempo. El procesador Jaguar puede ser hasta el doble de costoso cuando los datos se comparten entre clústeres y se usan operaciones lock.

Recomendaciones


Hay varios patrones clave que puede seguir para evitar compartir datos con capacidad de escritura entre núcleos:
  • Estructura de datos: cambie de usar una matriz de estructuras (AoS) a una estructura de matrices (SoA) donde cada subproceso opere sobre una estructura única.
  • Separe los datos de solo lectura de los datos de lectura/escritura.
  • Relleno (padding): puede rellenar una estructura con un múltiplo del tamaño de una línea de caché (64 bytes). Esto eliminará el uso compartido falso si cada subproceso opera sobre una estructura.
  • Recuento de referencias (shared_ptr, weak_ptr, etc.): ambos tienen una única variable de recuento de referencias que se comparte entre todas las instancias del objeto.
  • Sea muy claro sobre la propiedad de los datos. Cuando los datos se pasan a otro subproceso para su procesamiento, ese subproceso ahora es el propietario de los datos. Evite hacer cualquier cosa con los datos hasta que ese subproceso haya terminado con ellos.
  • Cola de trabajos compartida: intente evitar usar una cola de trabajos compartida por varios subprocesos, especialmente si los trabajos son pequeños. En su lugar, considere tener una cola de trabajos única para cada subproceso. Puede usar el algoritmo de robo de trabajo (work stealing) para ayudar a equilibrar la carga entre los subprocesos.

Apéndice: código


Lectura sin procesar

Carga atómica

Escritura sin procesar

Almacenamiento atómico

CAS atómico

Última modificación el 28 de agosto de 2026