multithreading concurrency x86 x86-64 hyperthreading

multithreading - ¿Qué se utilizará para el intercambio de datos entre subprocesos que se ejecutan en un Core con HT?



concurrency x86 (1)

Creo que tendrás un viaje de ida y vuelta a L1 . (No es lo mismo que store-> load forwarding dentro de un solo hilo, que es incluso más rápido que eso).

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

Actualización: consulte estas preguntas y respuestas para ver algunas pruebas experimentales de rendimiento y latencia.

Una tienda tiene que retirarse en el hilo de escritura, y luego comprometerse a L1 desde el búfer / cola de la tienda un tiempo después. 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 golpe L1 con los datos antiguos, y el subproceso de almacenamiento debería obtener los datos almacenados a través de store-> load forwarding.

Los datos de la tienda ingresan al búfer de la tienda cuando se ejecuta la tienda uop, pero no puede comprometerse con L1 hasta que se sepa que no es especulativo, es decir, se retira. Pero el búfer de la tienda también elimina la jubilación del ROB (el búfer de reordenamiento en el núcleo fuera de servicio) frente al 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 el búfer de la tienda se llene.

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 reordenamiento de memoria de Jeff Preshing atrapado en el código Act podría usarse 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 atómica de lectura-modificación-escritura debe hacer que su tienda sea visible globalmente (comprometerse con L1) como parte de su ejecución, de lo contrario no sería atómica. Mientras los datos no crucen un límite entre las líneas de caché, solo puede bloquear esa línea de caché. (AFAIK así es como las CPU implementan típicamente operaciones atómicas de RMW como lock add [mem], 1 o lock cmpxchg [mem], rax ).

De cualquier manera, una vez hecho, los datos estarán calientes en la memoria caché L1 del núcleo, donde cualquiera de los hilos puede recibir un impacto de memoria caché al cargarlo.

Sospecho que dos hyperthreads que realizan incrementos atómicos en un contador compartido (o cualquier otra operación lock , como xchg [mem], eax ) alcanzarí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 los cachés L1 de los dos núcleos (a través de L3).

movNT (no temporales) ordenadas débilmente omiten el caché y colocan sus datos en un búfer de relleno de línea. También desalojan la línea de L1 si estaba caliente en 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á en absoluto hasta que ingrese en un búfer de relleno. Entonces, probablemente sea lo mismo que una tienda móvil 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 los almacenes NT para una pequeña porción de datos, espera que otro hilo lea de inmediato.

Los éxitos L1 son posibles debido a la forma en que las CPU de Intel comparten el caché L1. Intel utiliza cachés L1 virtualmente indexados, etiquetados físicamente (VIPT) en la mayoría (¿todos?) De sus diseños. (p. ej., la familia Sandybridge ). Pero dado que los bits de índice (que seleccionan un conjunto de 8 etiquetas) están debajo del desplazamiento de página, se comporta exactamente como un caché PIPT (piense en ello como la traducción de los 12 bits bajos como un no- op), pero con la ventaja de velocidad de un caché VIPT: puede obtener las etiquetas 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 son 2 hilos del mismo proceso con la misma dirección virtual para una línea de caché, o si son dos procesos separados que mapean un bloque de memoria compartida a diferentes direcciones en cada proceso. Esta es la razón por la cual L1d puede ser (y es) competitivo por ambos hyperthreads sin riesgo de aciertos de caché falsos positivos. 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 aquí basado en la idea incorrecta de que Skylake había reducido la asociatividad L1. Es el L2 de Skylake que tiene 4 vías, frente a 8 vías en Broadwell y versiones anteriores. Aún así, la discusión sobre una respuesta más reciente podría ser de interés.

Intel x86 manual vol3, capítulo 11.5.6 documenta que Netburst (P4) tiene la opción de no funcionar de esta manera . El valor predeterminado es "Modo adaptativo", que permite a los procesadores lógicos dentro de un núcleo compartir datos.

Hay un "modo compartido":

En modo compartido, el caché de datos L1 se comparte de manera competitiva entre procesadores lógicos. Esto es cierto incluso si los procesadores lógicos usan registros CR3 y modos de paginación idénticos.

En el modo compartido, las direcciones lineales en el caché de datos L1 pueden tener un alias, lo que significa que una dirección lineal en el caché puede apuntar a diferentes ubicaciones físicas. El mecanismo para resolver el alias 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 microarquitectura Intel NetBurst que admite la tecnología Intel Hyper-Threading

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

El resto 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 Nehalem y la familia SnB.

En teoría, sería posible construir un núcleo de CPU OOO SMT que hiciera visibles las tiendas de un subproceso tan pronto como se retiraran, pero antes de que abandonen el búfer de la tienda y se comprometan con L1d (es decir, antes de que se vuelvan visibles globalmente). No es así como funcionan los diseños de Intel, ya que dividen estáticamente la cola de la tienda en lugar de compartirla de manera competitiva.

Incluso si los subprocesos compartían un búfer de tienda, el reenvío de tienda entre subprocesos para tiendas que aún no se han retirado no podría permitirse porque todavía son especulativos en ese punto. Eso uniría los dos hilos para predicciones erróneas de rama y otras reversiones.

El uso de una cola de almacenamiento compartida para varios subprocesos de hardware requeriría una lógica adicional para reenviar siempre las cargas del mismo subproceso, pero solo reenviar los almacenes retirados 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 por completo para tiendas no retiradas, ya que eso rompería el código de subproceso único.

Algunas CPU de potencia pueden hacer esto; parece ser la explicación más probable para que no todos los hilos estén de acuerdo en un solo pedido global para tiendas. ¿Se verán dos escrituras atómicas en diferentes ubicaciones en diferentes hilos siempre en el mismo orden por otros hilos? .

Como señala @BeeOnRope , esto no funcionaría para una CPU x86, solo para un ISA que no garantiza un pedido total de la tienda , ya que esto permitiría a los hermanos SMT ver su tienda antes de que sea visible globalmente para otros núcleos

El TSO podría preservarse tratando los datos de los almacenamientos intermedios de los hermanos como especulativos, o no pueden suceder antes de cualquier carga de caché perdida (porque las líneas que se mantienen calientes en su caché L1D no pueden contener nuevos almacenes de otros núcleos). IDK, no lo he pensado completamente. Parece demasiado complicado y probablemente no sea capaz de realizar reenvíos útiles mientras se mantiene el TSO, incluso más allá de las complicaciones de tener un búfer de tienda compartido o probar los búferes de tienda de hermanos.

Hyper-Threading tecnología Hyper-Threading es una forma de tecnología multiproceso simultánea introducida por Intel.

Estos recursos incluyen el motor de ejecución, los cachés y la interfaz del 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 los dos hilos comparten: almacenamiento en búfer, cachés L1 / L2 y bus del sistema.

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

¿Qué sucederá si ambos 2 hilos de un mismo proceso (el mismo espacio de direcciones virtuales) y si 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 línea de caché
  • 512 - líneas (512 = 32 KB / 64 B)
  • 8 vías
  • 64 - conjuntos de números de formas (64 = 512 líneas / 8 vías)
  • 6 bits [11: 6]: la dirección virtual (índice) define el número de conjunto actual (esta es la etiqueta)
  • 4 K: cada uno igual (dirección virtual / 4 K) compite por el mismo conjunto (32 KB / 8 vías)
  • 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