multithreading - ¿Lock xchg tiene el mismo comportamiento que mfence?
assembly x86 (1)
Creo que su pregunta es la misma que preguntar si
mfence
tiene la misma semántica de barrera que las instrucciones con
lock
de x86, o si proporciona menos
1
o garantías adicionales en algunos casos.
Mi mejor respuesta actual es que fue la
intención
de Intel y que la documentación de ISA garantiza que
mfence
y las instrucciones
lock
proporcionan la misma semántica de cercado, pero que debido a la supervisión de la implementación,
mfence
realidad proporciona una semántica de cercado más fuerte en hardware reciente (ya que al menos Haswell) .
En particular,
mfence
puede
mfence
una
carga no temporal
posterior de una región de memoria de tipo WC, mientras
lock
instrucciones
lock
no lo hacen.
Sabemos esto porque Intel nos dice esto en las erratas del procesador como HSD162 (Haswell) y SKL155 (Skylake) que nos dicen que las instrucciones bloqueadas no cierran una lectura no temporal posterior de la memoria WC:
MOVNTDQA de la memoria WC puede pasar instrucciones bloqueadas anteriores
Problema: puede parecer que una ejecución de (V) MOVNTDQA (instrucción de carga de transmisión) que se carga desde la memoria WC (combinación de escritura) pasa una instrucción bloqueada anterior que accede a una línea de caché diferente.
Implicación: el software que espera un bloqueo para cercar las instrucciones MOVNTDQA posteriores (V) puede no funcionar correctamente.
Solución alternativa: ninguna identificada. El software que se basa en una instrucción bloqueada para cercar ejecuciones posteriores de (V) MOVNTDQA debe insertar una instrucción MFENCE entre la instrucción bloqueada y la instrucción (V) MOVNTDQA posterior.
A partir de esto, podemos determinar que (1) Intel probablemente
pretendía
que las instrucciones bloqueadas cerraran las cargas de NT de la memoria de tipo WC, o de lo contrario esto no sería una errata
0.5
y (2) que las instrucciones bloqueadas en realidad
no
hacen eso, y Intel no pudo o decidió no arreglar esto con una actualización de microcódigo, y se recomienda
mfence
su lugar.
En Skylake,
mfence
realidad perdió su capacidad de cercado adicional con respecto a las cargas NT, según
SKL079: MOVNTDQA de WC Memory puede pasar instrucciones MFENCE anteriores
: tiene casi el mismo texto que la errata de instrucciones de
lock
, pero se aplica a
mfence
.
Sin embargo, el estado de esta errata es "Es posible que el BIOS contenga una solución para esta errata", que generalmente habla Intel para "una actualización de microcódigo aborda esto".
Esta secuencia de erratas quizás se pueda explicar por el momento: la errata de Haswell solo aparece a principios de 2016, años después del lanzamiento de ese procesador, por lo que podemos suponer que el problema llamó la atención de Intel una cantidad moderada de tiempo antes de eso.
En este punto, es casi seguro que Skylake ya estaba en libertad, aparentemente con una implementación de
mfence
menos conservadora que tampoco
mfence
las cargas de NT en las regiones de memoria de tipo WC.
Arreglar la forma en que las instrucciones bloqueadas funciona todo el camino de regreso a Haswell probablemente era imposible o costoso en función de su amplio uso, pero se necesitaba alguna forma de cercar las cargas de NT.
Aparentemente,
mfence
ya hizo el trabajo en Haswell, y Skylake se arreglaría para que
mfence
trabajara allí también.
Realmente no explica por qué apareció SKL079 (el de
mfence
) en enero de 2016, casi dos años antes de que apareciera SKL155 (el
locked
) a fines de 2017, o por qué este último apareció tanto después de la errata idéntica de Haswell.
Uno podría especular sobre lo que hará Intel en el futuro.
Dado que no pudieron / no quisieron cambiar las instrucciones de
lock
para Haswell a través de Skylake, que representan cientos de millones (¿miles de millones?) De chips desplegados, nunca podrán garantizar que las instrucciones bloqueadas cierren las cargas de NT, por lo que podrían considerar hacer Este es el comportamiento documentado y arquitectónico en el futuro.
O pueden actualizar las instrucciones bloqueadas, por lo que limitan tales lecturas, pero como cuestión práctica no puede confiar en esto probablemente durante una década o más, hasta que los chips con el comportamiento actual de no esgrima estén casi fuera de circulación.
Al igual que Haswell, según BV116 y BJ138 , las cargas NT pueden pasar instrucciones bloqueadas anteriormente en Sandy Bridge y Ivy Bridge, respectivamente. Es posible que las microarquitecturas anteriores también sufran este problema. Este "error" no parece existir en Broadwell y microarquitecturas después de Skylake.
Peter Cordes ha escrito un poco sobre el cambio de cerca de Skylake al final de esta respuesta .
La parte restante de esta respuesta es mi respuesta original, antes de saber acerca de la errata, y que se deja principalmente por interés histórico.
Vieja respuesta
Mi suposición informada sobre la respuesta es que
mfence
proporciona una funcionalidad de barrera adicional: entre los accesos que utilizan instrucciones débilmente ordenadas (p. Ej., Almacenes NT) y quizás entre los accesos
regiones
débilmente ordenadas (p. Ej., Memoria de tipo WC).
Dicho esto, esto es solo una suposición informada y encontrará detalles de mi investigación a continuación.
Detalles
Documentación
No está exactamente claro en qué medida los efectos de coherencia de memoria de
mfence
difieren de los proporcionados por la instrucción con prefijo de
lock
(incluido
xchg
con un operando de memoria, que está implícitamente bloqueado).
Creo que es seguro decir que únicamente con respecto a las regiones de memoria de
mfence
y sin involucrar ningún acceso no temporal,
mfence
proporciona la misma semántica de ordenación que la operación prefijada de
lock
.
Lo que está abierto a debate es si
mfence
difiere en absoluto de las instrucciones con prefijo de
lock
cuando se trata de escenarios fuera de lo anterior, en particular cuando los accesos involucran regiones distintas de las regiones WB o cuando están involucradas operaciones no temporales (transmisión).
Por ejemplo, puede encontrar algunas sugerencias (como
here
o
here
) de que
mfence
implica una semántica de barrera fuerte cuando están involucradas operaciones de tipo WC (por ejemplo, almacenes NT).
Por ejemplo, citando al Dr. McCalpin en este hilo (énfasis agregado):
La instrucción de vallado solo es necesaria para estar absolutamente seguro de que todos los almacenes no temporales son visibles antes de un almacén "ordinario" posterior. El caso más obvio donde esto importa es en un código paralelo, donde la "barrera" al final de una región paralela puede incluir una tienda "ordinaria". Sin una cerca, el procesador aún podría tener datos modificados en los búferes de combinación de escritura, pero pasar a través de la barrera y permitir que otros procesadores lean copias "obsoletas" de los datos combinados de escritura. Este escenario también podría aplicarse a un solo subproceso que migra el sistema operativo de un núcleo a otro (no estoy seguro de este caso).
No recuerdo el razonamiento detallado (todavía no hay suficiente café esta mañana), pero la instrucción que desea utilizar después de las tiendas no temporales es un MFENCE. De acuerdo con la Sección 8.2.5 del Volumen 3 del SWDM, el MFENCE es la única instrucción de cerca que evita que se ejecuten tanto las cargas posteriores como los almacenes posteriores antes de completar la cerca. Me sorprende que esto no se mencione en la Sección 11.3.1, que le dice lo importante que es garantizar la coherencia manualmente cuando se utiliza la combinación de escritura, ¡pero no le dice cómo hacerlo!
Veamos la sección de referencia 8.2.5 de Intel SDM:
Fortalecimiento o debilitamiento del modelo de ordenamiento de memoria
Las arquitecturas Intel 64 e IA-32 proporcionan varios mecanismos para fortalecer o debilitar el modelo de ordenamiento de memoria para manejar situaciones especiales de programación. Estos mecanismos incluyen:
• Las instrucciones de E / S, las instrucciones de bloqueo, el prefijo de BLOQUEO y las instrucciones de serialización fuerzan un pedido más fuerte en el procesador.
• La instrucción SFENCE (introducida en la arquitectura IA-32 en el procesador Pentium III) y las instrucciones LFENCE y MFENCE (introducidas en el procesador Pentium 4) brindan capacidades de orden y serialización de memoria para tipos específicos de operaciones de memoria.
Estos mecanismos se pueden usar de la siguiente manera:
Los dispositivos mapeados en memoria y otros dispositivos de E / S en el bus a menudo son sensibles al orden de escritura en sus memorias intermedias de E / S. Las instrucciones de E / S se pueden utilizar para (las instrucciones de ENTRADA y SALIDA) imponer un fuerte orden de escritura en los accesos de la siguiente manera. Antes de ejecutar una instrucción de E / S, el procesador espera a que se completen todas las instrucciones anteriores del programa y que todas las escrituras almacenadas en búfer se agoten en la memoria. Solo los recorridos de búsqueda de instrucciones y tablas de páginas pueden pasar instrucciones de E / S. La ejecución de las instrucciones posteriores no comienza hasta que el procesador determina que la instrucción de E / S se ha completado.
Los mecanismos de sincronización en sistemas de múltiples procesadores pueden depender de un modelo fuerte de ordenamiento de memoria. Aquí, un programa puede usar una instrucción de bloqueo como la instrucción XCHG o el prefijo LOCK para garantizar que una operación de lectura-modificación-escritura en la memoria se realice atómicamente. Las operaciones de bloqueo generalmente funcionan como operaciones de E / S, ya que esperan que se completen todas las instrucciones anteriores y que todas las escrituras almacenadas en el búfer se agoten en la memoria (consulte la Sección 8.1.2, “Bloqueo de bus”).
La sincronización del programa también se puede llevar a cabo con instrucciones de serialización (consulte la Sección 8.3). Estas instrucciones se usan típicamente en procedimientos críticos o límites de tareas para forzar la finalización de todas las instrucciones anteriores antes de que ocurra un salto a una nueva sección de código o un cambio de contexto. Al igual que las instrucciones de E / S y de bloqueo, el procesador espera hasta que se hayan completado todas las instrucciones anteriores y todas las escrituras almacenadas en búfer se hayan drenado en la memoria antes de ejecutar la instrucción de serialización.
Las instrucciones SFENCE, LFENCE y MFENCE proporcionan una forma eficiente de rendimiento de garantizar la carga y el almacenamiento de la memoria ordenada entre rutinas que producen resultados poco ordenados y rutinas que consumen esos datos . Las funciones de estas instrucciones son las siguientes:
• SFENCE: serializa todas las operaciones de almacenamiento (escritura) que ocurrieron antes de la instrucción SFENCE en la secuencia de instrucciones del programa, pero no afecta las operaciones de carga.
• LFENCE: serializa todas las operaciones de carga (lectura) que ocurrieron antes de la instrucción LFENCE en la secuencia de instrucciones del programa, pero no afecta las operaciones de almacenamiento.
• MFENCE: serializa todas las operaciones de almacenamiento y carga que ocurrieron antes de la instrucción MFENCE en la secuencia de instrucciones del programa.
Tenga en cuenta que las instrucciones SFENCE, LFENCE y MFENCE proporcionan un método más eficiente para controlar el orden de la memoria que la instrucción CPUID.
Contrariamente a la interpretación del Dr. McCalpin
2
, veo esta sección como algo ambigua en cuanto a si
mfence
hace algo extra.
Las tres secciones que hacen referencia a IO, instrucciones bloqueadas e instrucciones de serialización implican que proporcionan una barrera completa entre las operaciones de memoria antes y después de la operación.
No hacen ninguna excepción para la memoria débilmente ordenada y, en el caso de las instrucciones IO, uno también asumiría que necesitan trabajar de manera consistente con regiones de memoria débilmente ordenadas, ya que a menudo se usan para IO.
Luego, en la sección para las instrucciones de
FENCE
, se mencionan
explícitamente
las regiones de memoria débil: "Las instrucciones de SFENCE, LFENCE y MFENCE ** proporcionan una forma eficiente de rendimiento de asegurar la carga y almacenar el orden de la memoria entre rutinas que producen resultados y rutinas ordenadas débilmente consumir esos datos ".
¿Leemos entre líneas y entendemos que estas son las únicas instrucciones que logran esto y que las técnicas mencionadas anteriormente (incluidas las instrucciones bloqueadas) no ayudan a las regiones con poca memoria? Podemos encontrar algo de apoyo para esta idea al notar que las instrucciones de valla se introdujeron 3 al mismo tiempo que las instrucciones de almacenamiento no temporales ordenadas débilmente, y mediante un texto como el que se encuentra en 11.6.13 Instrucciones de sugerencias de caché que tratan específicamente con instrucciones ordenadas débilmente:
El grado en que un consumidor de datos sabe que los datos están mal ordenados puede variar para estos casos. Como resultado, la instrucción SFENCE o MFENCE debe usarse para garantizar el orden entre rutinas que producen datos con un orden débil y rutinas que consumen los datos. SFENCE y MFENCE proporcionan una forma eficiente de rendimiento para garantizar el pedido al garantizar que cada instrucción de la tienda que precede a SFENCE / MFENCE en el orden del programa sea visible globalmente antes de una instrucción de la tienda que sigue la cerca.
Nuevamente, aquí se mencionan específicamente las instrucciones de la cerca para que sean apropiadas para las instrucciones de cerca débilmente ordenadas.
También encontramos apoyo para la idea de que la instrucción bloqueada podría no proporcionar una barrera entre los accesos débilmente ordenados de la última oración ya citada anteriormente:
Tenga en cuenta que las instrucciones SFENCE, LFENCE y MFENCE proporcionan un método más eficiente para controlar el orden de la memoria que la instrucción CPUID.
cpuid
implica básicamente que las instrucciones de
FENCE
esencialmente reemplazan una funcionalidad ofrecida anteriormente por el
cpuid
serialización en términos de ordenamiento de memoria.
Sin embargo, si las instrucciones con prefijo de
lock
proporcionaran la misma capacidad de barrera que
cpuid
, probablemente habría sido la forma sugerida anteriormente, ya que en general son mucho más rápidas que
cpuid
que a menudo toma 200 o más ciclos.
La implicación es que había escenarios (probablemente escenarios de orden débil) que las instrucciones con prefijo de
lock
no manejaban, y dónde se usaba
mfence
, y donde ahora se sugiere
mfence
como reemplazo, lo que implica una semántica de barrera más fuerte que las instrucciones con prefijo de
lock
.
Sin embargo, podríamos interpretar algo de lo anterior de una manera diferente: tenga en cuenta que, en el contexto de las instrucciones de la cerca, a menudo se menciona que son una forma eficiente de garantizar el pedido. Por lo tanto, podría ser que estas instrucciones no pretendan proporcionar barreras adicionales, sino simplemente barreras más eficientes.
De hecho,
sfence
en unos pocos ciclos es mucho más rápido que las instrucciones de serialización como
cpuid
o instrucciones con prefijo de
lock
que generalmente son de 20 ciclos o más.
Por otro lado,
mfence
no es
generalmente más rápido que las instrucciones bloqueadas
4
, al menos en el hardware moderno.
Aún así, podría haber sido más rápido cuando se introdujo, o en algún diseño futuro, o tal vez se
esperaba
que fuera más rápido, pero eso no funcionó.
Por lo tanto, no puedo hacer una determinada evaluación basada en estas secciones del manual: creo que puede hacer un argumento razonable de que podría interpretarse de cualquier manera.
Podemos ver más a fondo la documentación de varias instrucciones de almacenamiento no temporales en la guía Intel ISA.
Por ejemplo, en la documentación de la tienda no temporal
movnti
encontrará la siguiente cita:
Debido a que el protocolo WC utiliza un modelo de consistencia de memoria débilmente ordenado, una operación de cercado implementada con la instrucción SFENCE o MFENCE debe usarse junto con las instrucciones MOVNTI si múltiples procesadores pueden usar diferentes tipos de memoria para leer / escribir las ubicaciones de memoria de destino.
La parte sobre "si varios procesadores pueden usar diferentes tipos de memoria para leer / escribir las ubicaciones de memoria de destino" es un poco confusa para mí. Esperaría que esto diga algo así como "para imponer el orden en el orden de escritura globalmente visible entre instrucciones usando pistas débilmente ordenadas" o algo así. De hecho, el tipo de memoria real (p. Ej., Según lo definido por el MTTR) probablemente ni siquiera entra en juego aquí: los problemas de ordenamiento pueden surgir únicamente en la memoria WB cuando se usan instrucciones ordenadas débilmente.
Actuación
Se
mfence
instrucción
mfence
toma 33 ciclos (latencia consecutiva) en las CPU modernas basadas en el tiempo de instrucción de Agner fog, pero una instrucción bloqueada más compleja como
lock cmpxchg
toma solo 18 ciclos.
Si
mfence
proporcionó una semántica de barrera no más fuerte que
lock cmpxchg
, este último está haciendo estrictamente más trabajo y no hay razón aparente para que
mfence
significativamente
más
.
Por supuesto, podría argumentar que
lock cmpxchg
es simplemente más importante que
mfence
y, por lo tanto, obtiene más optimización.
Este argumento se debilita por el hecho de que
todas
las instrucciones bloqueadas son considerablemente más rápidas que
mfence
, incluso las que se usan con poca frecuencia.
Además, se imagina que si hubiera una implementación de barrera única compartida por todas las instrucciones de
lock
,
mfence
simplemente usaría la misma, ya que es la validación más simple y fácil.
Entonces, el rendimiento más lento de
mfence
es, en mi opinión, evidencia significativa de que
mfence
está haciendo algo
más
.
0.5
Este no es un argumento hermético.
Pueden aparecer algunas cosas en las erratas que aparentemente son "por diseño" y no un error, como la dependencia falsa
popcnt
en el registro de destino, por lo que algunas erratas pueden considerarse una forma de documentación para actualizar las expectativas en lugar de implicar siempre un error de hardware.
1
Evidentemente, la instrucción con prefijo de
lock
también
realiza una operación atómica que no es posible lograr únicamente con
mfence
, por lo que las instrucciones con prefijo de
lock
definitivamente tienen una funcionalidad adicional.
Por lo tanto, para que
mfence
sea útil, esperaríamos que tenga semántica de barrera adicional en algunos escenarios
o
que funcione mejor.
2 También es completamente posible que estuviera leyendo una versión diferente del manual donde la prosa era diferente.
3
SFENCE
en SSE,
lfence
y
mfence
en SSE2.
4 Y a menudo es más lento: Agner lo tiene en la lista de latencia de 33 ciclos en hardware reciente, mientras que las instrucciones bloqueadas son generalmente de unos 20 ciclos.
Lo que me pregunto es si
lock xchg
tendrá un comportamiento similar a
mfence
desde la perspectiva de un hilo que accede a una ubicación de memoria que está siendo mutada (digamos al azar) por otros hilos.
¿Garantiza que obtengo el valor más actualizado?
De memoria leer / escribir instrucciones que siguen después?
La razón de mi confusión es:
8.2.2 "Las lecturas o escrituras no se pueden reordenar con instrucciones de E / S, instrucciones bloqueadas o instrucciones de serialización".
-Intel 64 Developers Manual vol. 3
¿Esto se aplica a través de hilos?
estados de
mfence
:
Realiza una operación de serialización en todas las instrucciones de carga desde memoria y almacenamiento en memoria emitidas antes de la instrucción MFENCE. Esta operación de serialización garantiza que cada instrucción de carga y almacenamiento que precede en orden de programa la instrucción MFENCE sea visible globalmente antes de que cualquier instrucción de carga o almacenamiento que siga a la instrucción MFENCE sea visible globalmente. La instrucción MFENCE se ordena con respecto a todas las instrucciones de carga y almacenamiento, otras instrucciones MFENCE, cualquier instrucción SFENCE y LFENCE, y cualquier instrucción de serialización (como la instrucción CPUID).
-Intel 64 Developers Manual Vol 3A
Esto
suena
como una garantía más fuerte.
Como parece que
mfence
está casi vaciando el búfer de escritura, o al menos llegando al búfer de escritura y otros núcleos para garantizar que mis futuras cargas / almacenes estén actualizados.
Cuando están marcadas en el banco, ambas instrucciones toman el orden de ~ 100 ciclos para completarse. Así que no puedo ver esa gran diferencia de ninguna manera.
Principalmente estoy confundido.
I instrucciones basadas en el
lock
utilizado en mutexes, pero luego no contienen vallas de memoria.
Luego veo
la
programación
sin bloqueo
que utiliza vallas de memoria, pero no bloqueos.
Entiendo que AMD64 tiene un modelo de memoria muy fuerte, pero los valores obsoletos pueden persistir en la memoria caché.
Si el
lock
no tiene el mismo comportamiento que
mfence
, ¿cómo le ayudan los mutexes a ver el valor más reciente?