C++ ¿Cómo se logra la liberación y adquisición en x86 utilizando únicamente MOV?
Esta pregunta es un seguimiento/aclaración de esto:
¿La instrucción MOV x86 implementa un almacén atómico C++11 Memory_order_release?
Esto indica que la MOV
instrucción de ensamblaje es suficiente para realizar la semántica de adquisición y liberación en x86. No necesitamos LOCK
vallas, xchg
etc. Sin embargo, me cuesta entender cómo funciona esto.
El capítulo 8 del documento Intel Vol 3A establece:
https://software.intel.com/sites/default/files/managed/7c/f1/253668-sdm-vol-3a.pdf
En un sistema de un solo procesador (núcleo)....
- Las lecturas no se reordenan con otras lecturas.
- Las escrituras no se reordenan con lecturas más antiguas.
- Las escrituras en la memoria no se reordenan con otras escrituras, con las siguientes excepciones:
pero esto es para un solo núcleo. La sección de múltiples núcleos no parece mencionar cómo se aplican las cargas:
En un sistema de múltiples procesadores, se aplican los siguientes principios de ordenamiento:
- Los procesadores individuales utilizan los mismos principios de ordenación que en un sistema de un solo procesador.
- Las escrituras de un solo procesador se observan en el mismo orden en todos los procesadores.
- Las escrituras de un procesador individual NO se ordenan con respecto a las escrituras de otros procesadores.
- El orden de la memoria obedece a la causalidad (el orden de la memoria respeta la visibilidad transitiva).
- Dos almacenes cualesquiera son vistos en un orden coherente por procesadores distintos de los que realizan los almacenes.
- Las instrucciones bloqueadas tienen un orden total.
Entonces, ¿cómo se puede MOV
por sí solo facilitar la adquisición-liberación?
pero esto es para un solo núcleo. La sección de múltiples núcleos no parece mencionar cómo se aplican las cargas:
El primer punto de esa sección es clave: los procesadores individuales utilizan los mismos principios de ordenación que en un sistema de un solo procesador. La parte implícita de esa declaración es ... al cargar/almacenar desde una memoria compartida coherente con caché. es decir, los sistemas multiprocesador no introducen nuevas formas de reordenamiento, solo significan que los posibles observadores ahora incluyen código en otros núcleos en lugar de solo dispositivos DMA/IO.
El modelo para reordenar el acceso a la memoria compartida es el modelo de un solo núcleo, es decir, orden del programa + un búfer de almacenamiento = básicamente acq_rel. En realidad, un poco más fuerte que acq_rel, lo cual está bien.
El único reordenamiento que ocurre es local , dentro de cada núcleo de CPU . Una vez que una tienda se vuelve visible globalmente, se vuelve visible para todos los demás núcleos al mismo tiempo y no fue visible para ningún núcleo antes de eso. (Excepto para el núcleo que hace la tienda, a través del reenvío de tienda). Es por eso que solo las barreras locales son suficientes para recuperar la coherencia secuencial además de un modelo SC + tienda-búfer. (Para x86, solo mo_seq_cst
necesita mfence
después de las tiendas SC, drenar el búfer de la tienda antes de que se puedan ejecutar más cargas.
mfence
Y lock
las instrucciones ed (que también son barreras completas) no tienen que molestar a otros núcleos, solo haga que este espere).
Un punto clave que hay que entender es que existe una visión compartida coherente de la memoria (a través de cachés coherentes) que comparten todos los procesadores. La parte superior del capítulo 8 del SDM de Intel define algunos de estos antecedentes:
Estos mecanismos de multiprocesamiento tienen las siguientes características:
- Para mantener la coherencia de la memoria del sistema: cuando dos o más procesadores intentan acceder simultáneamente a la misma dirección en la memoria del sistema, debe estar disponible algún mecanismo de comunicación o protocolo de acceso a la memoria para promover la coherencia de los datos y, en algunos casos, permitir que un procesador se bloquee temporalmente. una ubicación de memoria.
- Para mantener la coherencia de la caché: cuando un procesador accede a datos almacenados en caché en otro procesador, no debe recibir datos incorrectos. Si modifica datos, todos los demás procesadores que accedan a esos datos deben recibir los datos modificados.
- Para permitir un orden predecible de las escrituras en la memoria: en algunas circunstancias, es importante que las escrituras en la memoria se observen externamente exactamente en el mismo orden en que se programaron.
- [...]
El mecanismo de almacenamiento en caché y la coherencia del caché de los procesadores Intel 64 e IA-32 se analizan en el Capítulo 11.
(Las CPU usan alguna variante de MESI ; Intel en la práctica usa MESIF, AMD en la práctica usa MOESI).
El mismo capítulo también incluye algunas pruebas de fuego que ayudan a ilustrar/definir el modelo de memoria. Las partes que citó no son en realidad una definición estrictamente formal del modelo de memoria. Pero la sección 8.2.3.2 Ni las cargas ni los almacenes se reordenan con operaciones similares muestra que las cargas no se reordenan con cargas. Otra sección también muestra que está prohibido reordenar LoadStore . Acq_rel básicamente bloquea todos los reordenamientos excepto StoreLoad, y eso es lo que hace x86. ( https://preshing.com/20120913/acquire-and-release-semantics/ y https://preshing.com/20120930/weak-vs-strong-memory-models/ )
Relacionado:
- ¿Cómo se implementan microarquitectónicamente las barreras/vallas y la semántica de adquisición y liberación?
- barrera de memoria x86 mfence y C++ : pregunta por qué no se necesitan barreras para acq_rel, pero lo aborda desde un ángulo diferente (preguntándose cómo los datos se vuelven visibles para otros núcleos).
- ¿En qué se diferencian Memory_order_seq_cst y Memory_order_acq_rel? (seq_cst requiere vaciar el búfer de almacenamiento).
- ¿C11 Atomic Adquire/Release y x86_64 faltan de coherencia de carga/almacenamiento?
- Las instrucciones de carga globalmente invisibles program-order + store buffer no son exactamente lo mismo que acq_rel, especialmente una vez que consideras una carga que solo se superpone parcialmente a una tienda reciente.
- x86-TSO: un modelo de programador riguroso y utilizable para multiprocesadores x86 : un modelo de memoria formal para x86.
Otras ISA
En general, la mayoría de los modelos HW de memoria más débiles también permiten solo el reordenamiento local, por lo que las barreras siguen siendo solo locales dentro de un núcleo de CPU, lo que hace que (una parte de) ese núcleo espere hasta que se cumpla alguna condición. (por ejemplo, x86 mfence bloquea la ejecución de cargas y almacenes posteriores hasta que se agota el búfer del almacén. Otros ISA también se benefician de barreras livianas para la eficiencia de las cosas que x86 impone entre cada operación de memoria, por ejemplo, bloqueando el reordenamiento de LoadLoad y LoadStore. https://preshing .com/20120930/modelos-de-memoria-débiles-vs-fuertes/ )
Algunas ISA (sólo PowerPC en estos días) permiten que las tiendas se vuelvan visibles para otros núcleos antes de volverse visibles para todos, lo que permite el reordenamiento de IRIW . Tenga en cuenta que mo_acq_rel
en C++ se permite el reordenamiento IRIW; sólo seq_cst
lo prohíbe. La mayoría de los modelos de memoria HW son ligeramente más potentes que ISO C++ y lo hacen imposible, por lo que todos los núcleos coinciden en el orden global de las tiendas.
Actualizando la semántica de adquisición y liberación (citando cppreference en lugar del estándar, porque es lo que tengo a mano; el estándar es más... detallado, aquí):
Memory_order_acquire: una operación de carga con este orden de memoria realiza la operación de adquisición en la ubicación de memoria afectada: no se pueden reordenar lecturas ni escrituras en el hilo actual antes de esta carga. Todas las escrituras en otros subprocesos que liberan la misma variable atómica son visibles en el subproceso actual.
Memory_order_release: una operación de almacenamiento con este orden de memoria realiza la operación de liberación: no se pueden reordenar lecturas ni escrituras en el hilo actual después de este almacenamiento. Todas las escrituras en el hilo actual son visibles en otros hilos que adquieren la misma variable atómica
Esto nos da cuatro cosas que garantizar:
- adquirir orden: "no se pueden reordenar lecturas ni escrituras en el hilo actual antes de esta carga"
- pedido de lanzamiento: "no se pueden reordenar lecturas ni escrituras en el hilo actual después de esta tienda"
- sincronización de adquisición-liberación:
- "todas las escrituras en otros subprocesos que liberan la misma variable atómica son visibles en el subproceso actual"
- "todas las escrituras en el hilo actual son visibles en otros hilos que adquieren la misma variable atómica"
Revisión de las garantías:
- Las lecturas no se reordenan con otras lecturas.
- Las escrituras no se reordenan con lecturas más antiguas.
- Las escrituras en la memoria no se reordenan con otras escrituras [..]
- Los procesadores individuales utilizan los mismos principios de ordenación que en un sistema de un solo procesador.
Esto es suficiente para satisfacer las garantías del pedido.
Para realizar pedidos de adquisición, considere que se ha producido una lectura atómica: para ese hilo , claramente cualquier lectura o escritura posterior que migre antes violaría el primer o segundo punto, respectivamente.
Para el pedido de lanzamiento, considere que se ha producido una escritura atómica: para ese hilo , claramente cualquier lectura anterior o escritura que migre después violaría el segundo o tercer punto, respectivamente.
Lo único que queda es garantizar que si un hilo lee una tienda publicada, verá todas las demás cargas que el hilo del escritor había producido hasta ese punto. Aquí es donde se necesita la otra garantía multiprocesador.
- Las escrituras de un solo procesador se observan en el mismo orden en todos los procesadores.
Esto es suficiente para satisfacer la sincronización de adquisición y liberación.
Ya hemos establecido que cuando se produce la escritura de liberación, también habrán ocurrido todas las demás escrituras anteriores. Este punto garantiza que si otro hilo lee la escritura publicada , leerá todas las escrituras que el escritor produjo hasta ese momento. (Si no es así, entonces estaría observando que el procesador único escribe en un orden diferente al del procesador único, violando el punto).