cpu-architecture cpu-cache memory-barriers

cpu architecture - Instrucciones de carga invisible globalmente



cpu-architecture cpu-cache (3)

¿Algunas de las instrucciones de carga nunca pueden ser visibles globalmente debido al reenvío de carga de la tienda? Para decirlo de otra manera, si una instrucción de carga obtiene su valor del búfer de almacenamiento, nunca tiene que leer del caché.
Como generalmente se indica que una carga es visible globalmente cuando se lee desde la memoria caché L1D, las que no leen la L1D deberían hacerla invisible a nivel mundial.


El concepto de visibilidad global para las cargas es complicado, porque una carga no modifica el estado global de la memoria y otros subprocesos no pueden observarlo directamente .

Pero una vez que el polvo se asienta después de la ejecución fuera de orden / especulativa, podemos decir qué valor tiene la carga si el hilo lo almacena en algún lugar, o se ramifica en base a él. Este comportamiento observable del hilo es lo importante. (O podríamos observarlo con un depurador, y / o simplemente razonar acerca de qué valores podría ver una carga, si un experimento es difícil).

Al menos en CPU fuertemente ordenadas como x86, todas las CPU pueden ponerse de acuerdo en un orden total de las tiendas que se hacen visibles a nivel mundial , actualizando el estado de la memoria caché + coherente única y coherente. En x86, si no se permite la reordenación de StoreStore , este TSO (Orden de tienda total) está de acuerdo con el orden del programa de cada subproceso. (Es decir, el orden total es algún intercalado de orden de programa de cada hilo). SPARC TSO también está fuertemente ordenado.

(Para las tiendas que pasan por alto la memoria caché, la visibilidad global es cuando se descargan de los búferes de combinación de escritura no coherentes en la DRAM).

En una ISA débilmente ordenada, las hebras A y B pueden no estar de acuerdo con el orden de las tiendas X e Y hechas por las hebras C y D, incluso si las hebras de lectura usan cargas adquiridas para asegurarse de que sus propias cargas no se reordenan. es decir, puede que no exista un orden global de tiendas, y mucho menos que no sea el mismo que el orden del programa.

IBM POWER ISA es así de débil, y también lo es el modelo de memoria C ++ 11 ( ¿Las otras escrituras atómicas en diferentes ubicaciones en diferentes subprocesos siempre se verán en el mismo orden en otros subprocesos? ). Eso parece estar en conflicto con el modelo de las tiendas que se hacen visibles globalmente cuando se comprometen desde el búfer de la tienda a la caché L1d. Pero @BeeOnRope dice en comentarios que el caché es realmente coherente y permite recuperar la coherencia secuencial con barreras. Estos efectos de orden múltiple solo ocurren debido a que SMT (varias CPU lógicas en una CPU física) causan extraordinarias reordenaciones locales.

(Un posible mecanismo sería permitir que otros subprocesos lógicos vaguen almacenes no especulativos desde el búfer de almacenamiento incluso antes de que se comprometan con L1d, solo manteniendo los almacenes no retirados aún en un subproceso lógico. Esto podría reducir ligeramente la latencia entre subprocesos. X86 no se puede hacer esto porque rompería el fuerte modelo de memoria; el HT de Intel particiona de manera estática el búfer de la tienda cuando dos subprocesos están activos en un núcleo. Pero, como comenta @BeeOnRope, un modelo abstracto de qué reordenaciones están permitidas es probablemente un mejor enfoque para razonar acerca de la corrección. El hecho de que no pueda pensar en un mecanismo HW para provocar una reordenación no significa que no pueda suceder.

Sin embargo, las ISA mal ordenadas que no son tan débiles como POWER todavía se reordenan en el búfer de la tienda local de cada núcleo, si no se usan barreras o tiendas de liberación. En muchas CPU hay un orden global para todas las tiendas, pero no se trata de un intercalado de orden de programa. Las CPU de OoO tienen que rastrear el orden de la memoria, por lo que un solo hilo no necesita barreras para ver sus propias tiendas en orden, pero permitir que las tiendas se comprometan desde el búfer de la tienda a L1d fuera del orden del programa podría mejorar el rendimiento (especialmente si hay varias tiendas pendiente para la misma línea, pero el orden del programa desaloja la línea de un caché asociativo de conjuntos entre cada tienda. Por ejemplo, un patrón de acceso al histograma desagradable.)

Hagamos un experimento mental sobre de dónde provienen los datos de carga.

Lo anterior es solo acerca de la visibilidad de la tienda, no de las cargas. ¿podemos explicar el valor visto por cada carga como leída desde la memoria / caché global en algún momento (sin tener en cuenta las reglas de orden de carga)?

Si es así, entonces todos los resultados de la carga pueden explicarse poniendo todas las tiendas y cargas de todos los hilos en algún orden combinado, leyendo y escribiendo un estado global coherente de la memoria.

Resulta que no, no podemos, el búfer de la tienda rompe esto : el reenvío parcial de almacenamiento a carga nos da un contra-ejemplo (en x86, por ejemplo). Un almacén angosto seguido de una carga amplia puede combinar los datos del búfer del almacén con los datos de la memoria caché L1d desde antes de que el almacén se haga visible a nivel mundial. Las CPU x86 reales realmente hacen esto, y tenemos los experimentos reales para probarlo.

Si solo observa el reenvío completo de la tienda, donde la carga solo toma sus datos de una tienda en el búfer de la tienda, podría argumentar que la carga se retrasa por la memoria del almacén. es decir, que la carga aparece en el pedido global de almacenamiento de carga justo después del almacén que hace que ese valor sea visible globalmente.

(Este pedido global de carga total no es un intento de crear un modelo alternativo de pedido de memoria; no tiene manera de describir las reglas de orden de carga reales de x86).

El reenvío parcial de tienda expone el hecho de que los datos de carga no siempre provienen del dominio de caché coherente global.

Si una tienda de otro núcleo cambia los bytes circundantes, una carga atómica amplia podría leer un valor que nunca existió, y nunca existirá, en el estado coherente global.

Vea mi respuesta en ¿Puede x86 reordenar una tienda angosta con una carga más amplia que la contenga completamente? y la respuesta de Alex a la prueba experimental de que tal reordenación puede ocurrir, haciendo que el esquema de bloqueo propuesto en esa pregunta sea inválido. Una tienda y luego una recarga desde la misma dirección no es una barrera de memoria de StoreLoad .

Algunas personas (por ejemplo, Linus Torvalds) describen esto diciendo que el búfer de la tienda no es coherente . (Linus estaba respondiendo a otra persona que había inventado de forma independiente la misma idea de bloqueo no válida).

Otra pregunta y respuesta sobre el almacenamiento y la coherencia del almacenamiento: ¿Cómo establecer bits de un vector de bits de manera eficiente en paralelo? . Puede hacer algunos OR no atómicos para establecer bits, luego regresar y verificar las actualizaciones perdidas debido a conflictos con otros subprocesos. Pero necesita una barrera StoreLoad (por ejemplo, un lock or x86) para asegurarse de que no solo ve sus propias tiendas cuando vuelve a cargar.

Una carga se vuelve globalmente visible cuando lee sus datos. Normalmente de L1d, pero el búfer de almacenamiento o MMIO o la memoria no almacenable son otras fuentes posibles.

Esta definición concuerda con los manuales x86 que dicen que las cargas no se reordenan con otras cargas. es decir, se cargan (en orden de programa) desde la vista de memoria del núcleo local.

La carga en sí misma puede hacerse visible globalmente independientemente de si alguna otra hebra podría cargar ese valor desde esa dirección.


No estoy seguro de que la visibilidad global sea ​​un concepto interesante para las operaciones de carga (se requested aclaración), pero si desea usarlo para resolver algunos argumentos semánticos, tendrá que depender de las definiciones. Si, por ejemplo, su definición de visibilidad global para las cargas es el momento en que carga un valor de la memoria caché L1 y no admite la posibilidad de reenvío de almacenamiento, entonces la respuesta es "nunca se hace visible" o "su la definición es defectuosa ".

Sin embargo, como cuestión práctica, uno puede pensar en cargas que reciben su valor de alguna tienda particular en el sistema. De esta manera, podemos hablar de una visibilidad global para las tiendas (y tal vez un pedido parcial o total en estas tiendas) y luego discutir qué cargas pueden recibir su valor de las tiendas. De esta manera, la serie de valores recibidos por varias cargas los coloca en un tipo de tiempo global (aunque quizás solo se ordene parcialmente si las tiendas solo se ordenan parcialmente).

En este modelo, las cargas generalmente reciben su valor de alguna tienda visible a nivel mundial, pero en el caso especial de reenvío de tienda, la carga recibe su valor de una tienda que aún no es visible a nivel mundial . En la práctica, la tienda (o una tienda sucesora que la sobrescribe) o (a) se hará visible globalmente en algún momento, tal como se escribe en L1 desde el almacenamiento intermedio de la tienda o (b) se descartará debido a algún evento, como un error de especulación, una interrupción, una excepción, etc. En el caso de que la tienda se descarte, no debemos preocuparnos: una carga solo toma su valor de una tienda anterior en el orden del programa, por lo que cuando se descarta una tienda, todo Las instrucciones posteriores en el orden del programa también se descartan, incluida la carga.

En el caso de que la tienda asociada finalmente se haga globalmente visible, tiene un interesante efecto de tipo de viaje en el tiempo: la carga en la CPU local potencialmente ha visto a la tienda mucho antes que a otros procesadores, y en particular quizás la vea fuera de orden. Con respecto a otras tiendas en el sistema. Este efecto es una de las razones por las que los sistemas con reenvío de tienda generalmente tienen una reordenación asociada con él; por ejemplo, en el modelo de memoria x86 fuerte, los reordenes permitidos son exactamente los causados ​​por el almacenamiento en búfer y el reenvío de tienda.


Permítame ampliar un poco la pregunta y discutir el aspecto de corrección de la implementación del reenvío de carga de la tienda. (La segunda mitad de la respuesta de Peter responde directamente a la pregunta, creo).

El reenvío de carga de la tienda cambia la latencia de la carga, no su visibilidad. A menos que se haya enrojecido debido a alguna mala especulación, la tienda eventualmente se volverá globalmente visible de todos modos. Sin el reenvío de carga de la tienda, la carga debe esperar hasta que todas las tiendas en conflicto se retiren. Entonces la carga puede recuperar los datos normalmente.

(La definición exacta de un almacén conflictivo depende del modelo de pedido de memoria de la ISA. En x86, asumiendo el tipo de memoria WB, que permite el reenvío de carga de almacén, cualquier almacén que sea anterior en el orden del programa y cuya ubicación de memoria física de destino se superponga de la carga es una tienda conflictiva).

Aunque si hay algún almacén en conflicto concurrente de otro agente en el sistema, eso podría cambiar el valor cargado porque el almacén externo puede entrar en vigencia después del almacén local pero antes de la carga local. Normalmente, el búfer de la tienda no está en el dominio de coherencia, por lo que el reenvío de carga de la tienda puede reducir la probabilidad de que algo así suceda. Esto depende de las limitaciones de la implementación de reenvío de carga de tienda; por lo general no hay garantías de que el reenvío se realice para ninguna carga en particular y operaciones de almacenamiento.

El reenvío de carga de la tienda también puede resultar en órdenes de memoria global que no hubieran sido posibles sin ella. Por ejemplo, en el modelo sólido de x86, se permite la reordenación de la carga de almacenamiento y, junto con el reenvío de carga de la tienda, puede permitir que cada agente en el sistema vea todas las operaciones de memoria en diferentes órdenes.

En general, considere un sistema de memoria compartida con exactamente dos agentes. Sea S1 (A, B) el conjunto de posibles órdenes de memoria global para las secuencias A y B con reenvío de carga de almacén y sea S2 (A, B) el conjunto de posibles órdenes de memoria global para las secuencias A y B sin almacenamiento -envío de carga. Tanto S1 (A, B) como S2 (A, B) son subconjuntos del conjunto de todas las órdenes de memoria globales legales S3 (A, B). El reenvío de carga almacenada puede hacer que S1 (A, B) no sea un subconjunto de S2 (A, B). Esto significa que si S2 (A, B) = S3 (A, B), entonces el reenvío de carga de la tienda sería una optimización ilegal.

El reenvío de carga de la tienda puede cambiar la probabilidad de que ocurra cada orden de memoria global porque reduce la latencia de la carga.