assembly x86 memory-barriers

assembly - ¿Cuál es una mejor barrera de escritura en x86: lock+addl o xchgl?



memory-barriers (4)

El kernel de Linux usa lock; addl $0,0(%%esp) lock; addl $0,0(%%esp) como barrera de escritura, mientras que la biblioteca RE2 usa xchgl (%0),%0 como barrera de escritura. ¿Cuál es la diferencia y cuál es mejor?

¿Requiere x86 también instrucciones de lectura de barrera? RE2 define su función de barrera de lectura como no-operativa en x86, mientras que Linux la define como lfence o no lfence dependiendo de si SSE2 está disponible. ¿Cuándo se requiere la lfence ?


Aparte de las otras respuestas, los desarrolladores de HotSpot encontraron ese lock; addl $0,0(%%esp) lock; addl $0,0(%%esp) con un offset cero puede no ser óptimo, en algunos procesadores puede introducir dependencias de datos falsas ; jdk error relacionado.

Tocar una ubicación de pila con un desplazamiento diferente puede mejorar el rendimiento en algunas circunstancias.


Citando de los manuales de IA32 (Vol 3A, Capítulo 8.2: Orden de la memoria):

En un sistema de procesador único para regiones de memoria definidas como caché de escritura no simultánea, el modelo de ordenación de memoria respeta los siguientes principios [..]

  • Las lecturas no se reordenan con otras lecturas
  • Las escrituras no se reordenan con lecturas antiguas
  • Las escrituras en la memoria no se reordenan con otras escrituras, con la excepción de
    • escrituras ejecutadas con la instrucción CLFLUSH
    • almacenes de transmisión (escrituras) ejecutados con las instrucciones de movimiento no temporal ([lista de instrucciones aquí])
    • operaciones de cadena (ver Sección 8.2.4.1)
  • Las lecturas pueden reordenarse con escrituras anteriores en diferentes ubicaciones, pero no con escrituras anteriores en la misma ubicación.
  • Las lecturas o escrituras no pueden reordenarse con instrucciones de E / S, instrucciones bloqueadas o instrucciones de serialización
  • Las lecturas no pueden pasar las instrucciones LFENCE y MFENCE
  • Las escrituras no pueden pasar las instrucciones SFENCE y MFENCE

Nota: El "En un sistema de procesador único" anterior es ligeramente engañoso. Las mismas reglas son válidas para cada procesador (lógico) individualmente; el manual luego describe las reglas de pedido adicionales entre múltiples procesadores. Lo único que tiene que ver con la pregunta es que

  • Las instrucciones bloqueadas tienen un orden total.

En resumen, mientras esté escribiendo en la memoria de escritura no simultánea (que es toda la memoria que verá siempre que no sea un controlador o programador de gráficos), la mayoría de las instrucciones x86 son casi consistentes de forma secuencial: el único reordenamiento una CPU x86 puede realizar es reordenar lecturas posteriores (independientes) para ejecutar antes de escrituras. Lo principal de las barreras de escritura es que tienen un prefijo de lock (implícito o explícito), que prohíbe todo reordenamiento y garantiza que las operaciones se vean en el mismo orden por todos los procesadores en un sistema multiprocesador.

Además, en la memoria de reescritura, las lecturas nunca se reordenan, por lo que no hay necesidad de barreras de lectura. Los procesadores x86 recientes tienen un modelo de consistencia de memoria más débil para las tiendas de transmisión y la memoria de escritura combinada (comúnmente utilizada para la memoria de gráficos mapeados). Ahí es donde las diversas instrucciones de la fence entran en juego; no son necesarios para ningún otro tipo de memoria, pero algunos controladores en el kernel de Linux se ocupan de la memoria combinada de escritura por lo que simplemente definieron su barrera de lectura de esa manera. La lista del modelo de pedido por tipo de memoria se encuentra en la Sección 11.3.1 en el vol. 3A de los manuales IA-32. Versión corta: Write-Through, Write-Back y Write-Protected permiten lecturas especulativas (siguiendo las reglas detalladas anteriormente), Uncachable y Strong Uncacheable memory tiene fuertes garantías de pedido (no se realiza ninguna operación de reordenación, lectura o escritura del procesador, se usa para MMIO ) y Escribir La memoria combinada tiene un orden débil (es decir, reglas de orden relajadas que necesitan cercas).


El " bloqueo; addl $ 0,0 (%% esp) " es más rápido en caso de que probemos el estado 0 de la variable de bloqueo en la dirección (%% esp). Porque agregamos 0 valor a la variable de bloqueo y el indicador de cero se establece en 1 si el valor de bloqueo de la variable en la dirección (%% esp) es 0.

Información acerca de la hoja de datos de Intel:

Realiza una operación de serialización en todas las instrucciones de carga desde la memoria que se emitieron antes de la instrucción LFENCE. Esta operación de serialización garantiza que cada instrucción de carga que precede en el orden del programa, la instrucción LFENCE esté visible globalmente antes de que cualquier instrucción de carga que siga a la instrucción LFENCE sea visible globalmente.

Por ejemplo: las instrucciones de escritura de memoria como ''mov'' son atómicas (no necesitan el prefijo de bloqueo) si están alineadas correctamente. Pero esta instrucción normalmente se ejecuta en la memoria caché de la CPU y no será visible en este momento para todos los demás hilos, ya que la valla de memoria se debe preformar primero.

EDITAR:

Entonces, la principal diferencia entre estas dos instrucciones es que la instrucción xchgl no tendrá ningún efecto en los indicadores condicionales. Ciertamente, podemos probar el estado de la variable de bloqueo con la instrucción lock cmpxchg , pero esto es aún más complejo que con lock add $ 0 instruction.


La parte importante de la lock; addl lock; addl y xchgl es el prefijo de lock . Está implícito para xchgl . Realmente no hay diferencia entre los dos. Me gustaría ver cómo se ensamblan y elegir el que es más corto (en bytes), ya que suele ser más rápido para operaciones equivalentes en x86 (por lo tanto, trucos como xorl eax,eax )

La presencia de SSE2 probablemente sea solo un proxy de la condición real que, en última instancia, es una función de cpuid . Probablemente resulte que SSE2 implica la existencia de una lfence y la disponibilidad de SSE2 fue revisada / almacenada en caché durante el arranque. Se requiere una lfence cuando está disponible.