Pregunta

Linux kernel utiliza el lock; addl $0,0(%%esp) como barrera de escritura, mientras que los usos de la biblioteca RE2 xchgl (%0),%0 como barrera de escritura. Cuál es la diferencia y lo que es mejor?

¿El x86 también requieren instrucciones de barrera de lectura? RE2 define su función de barrera de lectura como un no-op en x86 mientras que Linux lo define como sea lfence o no operación dependiendo de si está disponible SSE2. Cuando se requiere lfence?

¿Fue útil?

Solución

El " cerradura; addl $ 0,0 (%% ESP) " es más rápida en caso de que la prueba 0 estado de bloqueo variable en la dirección (%% esp). Debido a que añadimos valor 0 a la variable de bloqueo y la bandera de cero se pone a 1 si el valor de la variable de bloqueo en la dirección (%% ESP) es 0.


lfence de Intel ficha técnica:

  

realiza una operación de serialización en   todas las instrucciones de carga de memoria que   fueron emitidos antes de la LFENCE   instrucción. esta serialización   garantías de funcionamiento que cada carga   instrucción que preceda en el programa de   ordenar la instrucción es LFENCE   globalmente visible antes de cualquier carga   la instrucción que sigue a la LFENCE   la instrucción es visible globalmente.

( Nota del editor: mfence o una operación de locked es la valla sólo es útil (después de una tienda) para secuencial consistencia . lfence hace no bloque StoreLoad reordenamiento por la memoria intermedia de almacenamiento.)


Por ejemplo: instrucción de escritura de memoria como 'mov' son atómicos (que no necesitan prefijo de bloqueo) si no están correctamente alineados. Sin embargo, esta instrucción se ejecuta normalmente en la memoria caché de la CPU y no será visible globalmente en este momento para todos los otros hilos, porque valla de memoria debe realizarse primero en hacer este hilo esperar hasta las tiendas anteriores son visibles para otros hilos.


Así que la principal diferencia entre estas dos instrucciones es que xchgl instrucción no tendrá ningún efecto sobre las banderas condicionales. Ciertamente, podemos probar el estado de bloqueo variable con Bloqueo cmpxchg de instrucciones pero esto es aún más complejo que con Bloqueo de añadir $ 0 de instrucciones.

Otros consejos

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

  

En un sistema de un solo procesador para regiones de memoria definida como cacheable write-back, la memoria de pedidos modelo respeta los siguientes principios [..]

     
      
  • las lecturas no se reordenan con otra lee
  •   
  • Las escrituras no se reordenan, con lecturas mayores
  •   
  • Las escrituras en la memoria no se reordenan con otras escrituras, con la excepción de   
        
    • escribe ejecuta con la instrucción CLFLUSH
    •   
    • streaming de tiendas (escrituras) ejecutada con las instrucciones de movimiento no temporales ([lista de instrucciones aquí])
    •   
    • operaciones de cadena (véase la Sección 8.2.4.1)
    •   
  •   
  • Reads puede reordenarse con las escrituras de edad a diferentes lugares, pero no con las escrituras más antiguas en la misma ubicación.
  •   
  • lee o escribe no puede ser reordenada con las instrucciones de E / S, instrucciones bloqueadas, o instrucciones serialización
  •   
  • Lee no puede pasar instrucciones LFENCE y MFENCE
  •   
  • Las escrituras no puede pasar instrucciones SFENCE y MFENCE
  •   

Nota: El "En un sistema de un solo procesador de" arriba está ligeramente engañosa. Las mismas reglas válidas para cada procesador (lógico) de forma individual; el manual luego pasa a describir las reglas de ordenación adicionales entre varios procesadores. La única parte de ella perteneciente a la pregunta es que

  
      
  • instrucciones bloqueadas tienen un orden total.
  •   

En resumen, siempre y cuando se está escribiendo en la memoria de escritura no simultánea (que es toda la memoria que jamás ver, siempre y cuando no eres un conductor o gráficos programador), la mayoría de las instrucciones x86 son casi secuencialmente consistente - la única reordenación de una CPU x86 puede realizar es de reordenación posterior (independiente) lee antes de ejecutar escrituras. Lo principal de las barreras de escritura es que tienen un prefijo lock (implícito o explícito), que prohíbe toda reordenamiento y asegura que las operaciones se ve en el mismo orden de todos los procesadores de un sistema multiprocesador.

Además, en la memoria de escritura no simultánea, las lecturas se no reordenado, así que no hay necesidad de barreras de leer. procesadores x86 recientes tienen una consistencia de datos de memoria más débil para la transmisión de tiendas y escritura de la memoria-combinada (comúnmente utilizado para la memoria gráfica asignada). Ahí es donde las diversas instrucciones fence entran en juego; no son necesarios para cualquier otro tipo de memoria, pero algunos conductores en el núcleo de Linux hacen acuerdo con la memoria de escritura-combinada por lo que sólo definen su lectura barrera de esa manera. La lista de modelo de ordenación según el tipo de memoria es en la Sección 11.3.1 en vol. 3A de los IA-32 manuales. Versión corta: escritura simultánea, Write-Back y protegido contra escritura permitir especulativa lee (siguiendo las reglas que se detallan más arriba), la memoria no almacenable en caché uncachable y Strong tiene fuertes garantías de pedido (sin reordenamiento procesador, lecturas / escrituras son inmediatamente ejecutados, utilizados para MMIO ) y escribir en la memoria combinada tiene pedidos débil (es decir, reglas de ordenación relajadas que las cercas necesidad).

lock addl $0, (%esp) es un sustituto de mfence, no lfence.

El caso de uso es cuando se necesita para bloquear StoreLoad reordenamiento (la única clase que el fuerte modelo de memoria de x86 permite), pero no se necesita una operación atómica RSR en una variable compartida. https://preshing.com/20120515/memory-reordering-caught- en-el-acto /

por ejemplo. suponiendo std::atomic<int> a,b alineado:

movl   $1, a             a = 1;    Atomic for aligned a
# barrier needed here
movl   b, %eax           tmp = b;  Atomic for aligned b

Sus opciones son:

  • hacer una tienda secuencial-coherencia con xchg , por ejemplo mov $1, %eax / xchg %eax, a por lo que no necesita una barrera separada; que es parte de la tienda. Creo que esta es la opción más eficiente en la mayoría del hardware moderno; C ++ 11 compiladores distintos de uso gcc xchg para tiendas de seq_cst.
  • Uso mfence como una barrera. (Usos gcc mov + mfence para tiendas de seq_cst).
  • Uso lock addl $0, (%esp) como una barrera. Cualquier instrucción locked es una barrera completa. cerradura ¿El xchg tiene el mismo comportamiento que mfence?

    (o en alguna otra ubicación, pero la pila está casi siempre privada y caliente en L1d, así que es un poco buen candidato. Sin embargo, esto puede crear una cadena de dependencias para algo usando los datos en la parte inferior de la pila.)

Sólo se puede utilizar como una barrera xchg doblándola en una tienda porque incondicionalmente escribe la posición de memoria con un valor que no depende del valor de edad.

Cuando sea posible, el uso de xchg para una tienda ss-cst es probablemente el mejor, a pesar de que también lee desde la ubicación compartida. mfence es más lento de lo esperado en las CPU Intel reciente ( Son cargas y almacena las únicas instrucciones que consigue reordenados? ), bloqueando también fuera de orden de ejecución de instrucciones que no sean de memoria independientes de la misma manera lfence hace.

Incluso podría valer la pena utilizar en lugar de lock addl $0, (%esp)/(%rsp) mfence incluso cuando mfence está disponible, pero no he experimentado con las desventajas. Usando -64(%rsp) o algo podría hacer que sea menos probable que alargar una dependencia de datos en algo caliente (a una dirección de retorno local o), pero que puede hacer herramientas como valgrind infeliz.


lfence no es útil para el pedido memoria, a menos que usted está leyendo desde la RAM de vídeo (o algún otro WC débilmente ordenada región) con cargas MOVNTDQA.

La serialización de ejecución fuera de orden (pero no la memoria intermedia de almacenamiento) no es útil para detener StoreLoad reordenamiento (la única clase que el fuerte modelo de memoria de x86 permite BM normal (write-back) regiones de memoria).

Los casos de uso del mundo real para lfence son para el bloqueo fuera de orden de ejecución de rdtsc de tiempo muy cortos bloques de código, o para la mitigación del espectro mediante el bloqueo de la especulación a través de una bifurcación condicional o indirecta.

Cuando debo usar _mm_sfence _mm_lfence y _mm_mfence (mi respuesta y la respuesta de @ BeeOnRope) para más información sobre qué lfence no es útil, y cuándo usar cada una de las instrucciones de barrera. (O en la mía, los intrínsecos C ++ al programar en C ++ en lugar de ASM).

Como acotación al margen de las otras respuestas, los desarrolladores HotSpot encontró que lock; addl $0,0(%%esp) con un decalaje de origen puede no ser óptima, en algunos procesadores que puede introducir dependencias de datos falsos ; JDK error .

Al tocar una ubicación pila con un desplazamiento puede mejorar el rendimiento en algunas circunstancias diferentes.

La parte importante de lock; addl y xchgl es el prefijo lock. Es implícito para xchgl. En realidad no hay diferencia entre los dos. Me vería en la forma en que se reúnen y elegir el que sea más corto (en bytes) ya que es generalmente más rápido para las operaciones equivalentes en x86 (de ahí trucos como xorl eax,eax)

La presencia de SSE2 es probablemente sólo un indicador de la condición real que es en última instancia una función de cpuid. Probablemente resulta que SSE2 implica la existencia de lfence y la disponibilidad de SSE2 se comprobó / en caché en el arranque. Se requiere lfence cuando esté disponible.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top