¿Qué se usará para el intercambio de datos entre hilos que se ejecutan en un Core con HT?

La tecnología Hyper-Threading es una forma de tecnología de subprocesamiento múltiple simultánea introducida por Intel.

Estos recursos incluyen el motor de ejecución, las memorias caché y la interfaz de bus del sistema; el intercambio de recursos permite que dos procesadores lógicos trabajen entre sí de manera más eficiente y permite que un procesador lógico estancado tome prestados recursos del otro.

En la CPU Intel con Hyper-Threading, un CPU-Core (con varias ALU) puede ejecutar instrucciones de 2 hilos en el mismo reloj. Y ambos hilos comparten: buffer de almacenamiento, cachés L1 / L2 y bus del sistema.

Pero si dos subprocesos se ejecutan simultáneamente en un Núcleo, el subproceso-1 almacena el valor atómico y el subproceso-2 carga este valor, ¿qué se usará para este intercambio: almacenamiento-memoria compartida, memoria caché compartida L1 / L2 o como memoria caché habitual L3?

¿Qué sucederá si ambos 2 hilos de un mismo proceso (el mismo espacio de direcciones virtuales) y si provienen de dos procesos diferentes (el diferente espacio de direcciones virtuales)?

Sandy Bridge Intel CPU – caché L1:

  • 32 KB – tamaño de caché
  • 64 B – tamaño de la línea de caché
  • 512 líneas (512 = 32 KB / 64 B)
  • 8 vías
  • 64 – conjuntos de formas numéricas (64 = 512 líneas / 8 vías)
  • 6 bits [11: 6] – de la dirección virtual (índice) define el número de conjunto actual (esta es la etiqueta)
  • 4 K – cada uno lo mismo (dirección virtual / 4 K) compite por el mismo conjunto (32 KB / 8-way)
  • 12 bits bajos: significativo para determinar el número de conjunto actual

  • 4 KB: tamaño de página estándar

  • 12 bits bajos, lo mismo en direcciones virtuales y físicas para cada dirección

enter image description here

Creo que obtendrás un viaje de ida y vuelta a L1 . (No es lo mismo que almacenar-> reenvío de carga dentro de un único hilo, que es incluso más rápido que eso).

El manual de optimización de Intel dice que los almacenamientos intermedios de almacenamiento y carga están divididos estáticamente entre subprocesos , lo que nos dice mucho sobre cómo funcionará. No he probado la mayor parte de esto, por lo tanto, avíseme si mis predicciones no coinciden con el experimento.

Actualización: consulte esta sección Preguntas y respuestas para realizar algunas pruebas experimentales de rendimiento y latencia.


Una tienda tiene que retirarse en el hilo de escritura y luego comprometerse con L1 desde el búfer / cola de la tienda algún tiempo después de eso. En ese punto, será visible para el otro subproceso, y una carga a esa dirección desde cualquiera de los subprocesos debería golpear en L1. Antes de eso, el otro subproceso debería obtener un hit L1 con los datos antiguos, y el hilo de almacenamiento debería obtener los datos almacenados a través de store-> load forwarding.

Los datos de la tienda ingresan en el búfer de la tienda cuando se ejecuta la tienda uop, pero no puede comprometerse con la L1 hasta que se sepa que no es especulativa, es decir, se retira. Pero el buffer de la tienda también desvincula la jubilación del ROB (el ReOrder Buffer en el núcleo fuera de servicio) con el compromiso con L1, que es ideal para las tiendas que faltan en el caché. El núcleo fuera de servicio puede seguir funcionando hasta que se llene la memoria intermedia de la tienda.


Dos subprocesos que se ejecutan en el mismo núcleo con hyperthreading pueden ver el reordenamiento de StoreLoad si no usan vallas de memoria, porque el reenvío de tienda no ocurre entre subprocesos. El código de Representar la Memoria de Jeff Preshing Atrapado en la Ley podría utilizarse para probarlo en la práctica, utilizando la afinidad de la CPU para ejecutar los hilos en diferentes CPU lógicas del mismo núcleo físico.

Una operación de lectura, modificación y escritura atómica tiene que hacer que su tienda sea visible globalmente (compromiso con L1) como parte de su ejecución, de lo contrario no sería atómica. Siempre que los datos no crucen un límite entre las líneas de caché, simplemente puede bloquear esa línea de caché. (AFAIK así es como las CPU implementan típicamente operaciones RMW atómicas como lock add [mem], 1 o lock cmpxchg [mem], rax ).

De cualquier manera, una vez que esté listo, los datos estarán calientes en la memoria caché L1 del núcleo, donde cualquiera de los hilos puede obtener un golpe de caché al cargarlo.

Sospecho que dos hyperthreads haciendo incrementos atómicos en un contador compartido (o cualquier otra operación de lock , como xchg [mem], eax ) obtendrían aproximadamente el mismo rendimiento que un solo hilo. Esto es mucho más alto que para dos subprocesos que se ejecutan en núcleos físicos separados, donde la línea de caché tiene que rebotar entre las cachés L1 de los dos núcleos (a través de L3).

movNT (no temporales) con orden débil omiten la memoria caché y colocan sus datos en un búfer de relleno de línea. También desalojaron la línea de L1 si estaba caliente en la memoria caché para empezar. Probablemente tengan que retirarse antes de que los datos entren en un búfer de relleno, por lo que una carga del otro hilo probablemente no lo verá hasta que ingrese en un búfer de relleno. Entonces, probablemente sea lo mismo que una tienda movnt seguida de una carga dentro de un solo hilo. (es decir, un viaje de ida y vuelta a DRAM, unos cientos de ciclos de latencia). No use las tiendas NT para obtener un pequeño dato que espera que otro hilo lea de inmediato.


Los hits L1 son posibles debido a la forma en que las CPU Intel comparten el caché L1. Intel utiliza cachés L1 prácticamente indexados, físicamente etiquetados (VIPT) en la mayoría (¿todos?) De sus diseños. (Por ejemplo, la familia Sandybridge ). Pero dado que los bits de índice (que seleccionan un conjunto de 8 tags) están por debajo del desplazamiento de página, se comporta exactamente como un caché PIPT (piénselo como la traducción de los 12 bits bajos siendo un no- op), pero con la ventaja de velocidad de un caché VIPT: puede buscar las tags de un conjunto en paralelo con la búsqueda TLB para traducir los bits superiores. Consulte el párrafo “L1 también usa trucos de velocidad que no funcionarían si fuera más grande” en esta respuesta .

Dado que el caché L1d se comporta como PIPT, y la misma dirección física realmente significa la misma memoria, no importa si se trata de 2 hilos del mismo proceso con la misma dirección virtual para una línea de caché, o si se trata de dos procesos separados mapeando un bloque de memoria compartida a diferentes direcciones en cada proceso. Esta es la razón por la que L1d puede ser (y es) competitivo para ambos hyperthreads sin riesgo de falsos positivos de caché. A diferencia del dTLB, que necesita etiquetar sus entradas con un ID de núcleo.

Una versión anterior de esta respuesta tenía un párrafo basado en la idea incorrecta de que Skylake había reducido la asociatividad L1. Es L2 de Skylake que es de 4 vías, frente a 8 vías en Broadwell y antes. Aún así, la discusión sobre una respuesta más reciente podría ser de interés.


El manual vol. X86 de Intel, capítulo 11.5.6 documenta que Netburst (P4) tiene una opción para no funcionar de esta manera . El valor predeterminado es “Modo adaptativo”, que permite que los procesadores lógicos dentro de un núcleo compartan datos.

Hay un “modo compartido”:

En modo compartido, la memoria caché de datos L1 se comparte competitivamente entre los procesadores lógicos. Esto es cierto incluso si los procesadores lógicos usan registros CR3 y modos de búsqueda idénticos.

En el modo compartido, las direcciones lineales en la memoria caché de datos L1 se pueden alias, lo que significa que una dirección lineal en la memoria caché puede apuntar a diferentes ubicaciones físicas. El mecanismo para resolver el aliasing puede conducir a la agitación. Por este motivo, IA32_MISC_ENABLE [bit 24] = 0 es la configuración preferida para procesadores basados ​​en la microarchitecture Intel NetBurst que admiten la tecnología Intel Hyper-Threading

No dice nada sobre esto para hyperthreading en Nehalem / SnB uarches, así que supongo que no incluyeron soporte de “modo lento” cuando introdujeron soporte HT en otro uarch, ya que sabían que habían obtenido el “modo rápido” para funciona correctamente en netburst. Me pregunto si este modo solo existía en caso de que descubrieran un error y tuvieran que deshabilitarlo con las actualizaciones de microcódigo.

El rest de esta respuesta solo aborda la configuración normal para P4, que estoy bastante seguro de que también es la forma en que funcionan las CPU de la familia Nehalem y SnB.


En teoría, sería posible construir un núcleo de CPU SMT de OOO que hiciera que las tiendas de un hilo fueran visibles para el otro tan pronto como se retiraran, pero antes abandonaran el buffer de la tienda y se comprometerían con L1d (es decir, antes de que se vuelvan visibles globalmente). No es así como funcionan los diseños de Intel, ya que parten estáticamente la cola de la tienda en lugar de compartirla competitivamente.

Incluso si los subprocesos compartieran un búfer de tienda, el reenvío de tienda entre subprocesos para tiendas que aún no se han retirado no se pudo permitir porque todavía son especulativas en ese punto. Eso uniría los dos hilos para errores erróneos de twigs y otros retrocesos.

Usar una cola de tienda compartida para múltiples subprocesos de hardware tomaría una lógica extra para reenviar siempre las cargas desde el mismo subproceso, pero solo reenviar las tiendas retiradas a las cargas de los otros subprocesos. Además del recuento de transistores, esto probablemente tendría un costo de energía significativo. No podría simplemente omitir el reenvío de tiendas para tiendas no retiradas, porque eso rompería el código de un solo subproceso.

Algunas CPU POWER realmente pueden hacer esto; parece ser la explicación más probable para que no todos los hilos estén de acuerdo en un único pedido global para las tiendas. ¿Siempre se verán dos secuencias atómicas en diferentes ubicaciones en diferentes hilos en el mismo orden por otros hilos? .

Como señala @BeeOnRope , esto no funcionaría para una CPU x86, solo para una ISA que no garantiza un Pedido de tienda total , porque esto permitiría que los hermanos SMT vean tu tienda antes de que se vuelva visible globalmente. otros núcleos.

TSO podría conservarse tratando los datos de los almacenamientos intermedios hermanos como especulativos o no antes de que se descarguen las cargas de caché (porque las líneas que permanecen calientes en su caché L1D no pueden contener nuevas tiendas de otros núcleos). IDK, no lo he pensado bien. Parece demasiado complicado y probablemente no sea capaz de realizar un reenvío útil mientras se mantiene TSO, incluso más allá de las complicaciones de tener un almacenamiento compartido en el búfer o una prueba en los almacenes hermanos.