multithreading assembly x86 cpu memory-fences

multithreading - ¿Cuándo se requieren las instrucciones x86 LFENCE, SFENCE y MFENCE?



assembly cpu (1)

Ok, he estado leyendo las siguientes Qs de SO con respecto a las vallas de la CPU x86 ( LFENCE , SFENCE y MFENCE ):

y:

y debo ser honesto, todavía no estoy totalmente seguro de cuándo se requiere una valla. Estoy tratando de entender desde la perspectiva de eliminar los bloqueos completamente soplados y tratar de usar un bloqueo granular más fino a través de vallas, para minimizar los retrasos de latencia.

En primer lugar, hay dos preguntas específicas que no entiendo:

A veces, al hacer una tienda, una CPU escribirá en su búfer de tienda en lugar de la memoria caché L1. Sin embargo, no entiendo los términos en los que una CPU hará esto.

CPU2 puede desear cargar un valor que haya sido escrito en el almacenamiento intermedio de almacenamiento de CPU1. Según tengo entendido, el problema es que la CPU2 no puede ver el nuevo valor en el almacenamiento intermedio de almacenamiento de la CPU1. ¿Por qué no puede el protocolo MESI simplemente incluir búferes de tienda de descarga como parte de su protocolo?

En términos más generales, ¿podría alguien intentar describir el escenario general y ayudar a explicar cuándo se requieren las instrucciones LFENCE / MFENCE y SFENCE ?

NB Uno de los problemas que se leen en torno a este tema es la cantidad de artículos escritos "en general" para múltiples arquitecturas de CPU, cuando solo estoy interesado específicamente en la arquitectura Intel x86-64.


La respuesta más simple: debe usar una de las 3 vallas ( LFENCE , SFENCE , MFENCE ) para proporcionar uno de los 6 datos Consistencia:

  • Relajado
  • Consumir
  • Adquirir
  • Lanzamiento
  • Adquisición-Liberación
  • Secuencial

C ++ 11:

Inicialmente, debería considerar este problema desde el punto de vista del grado de acceso a la memoria, que está bien documentado y estandarizado en C ++ 11. Deberías leer primero: http://en.cppreference.com/w/cpp/atomic/memory_order

x86 / x86_64:

1. Adquirir-Liberar consistencia: Entonces, es importante entender que en el x86 para acceder a la RAM convencional (marcado por defecto como WB - Write Back, y el mismo efecto con WT (Write Throught) o UC (Uncacheable)) por el uso de MOV asm sin ningún comando adicional proporciona automáticamente el orden de la memoria para Adquisición-Liberación de consistencia - std::memory_order_acq_rel . Es decir, para esta memoria tiene sentido usar solo std::memory_order_seq_cst solo para proporcionar consistencia secuencial. Es decir, cuando está usando: std::memory_order_relaxed o std::memory_order_acq_rel entonces el código ensamblador compilado para std::atomic::store() (o std::atomic::load() ) será el mismo - solo MOV sin cualquier L/S/MFENCE .

Nota: Pero debe saber que no solo la CPU sino también el compilador C ++ pueden reordenar las operaciones con la memoria y las 6 barreras de memoria siempre afectan al compilador C ++ independientemente de la arquitectura de la CPU.

Entonces, debe saber cómo puede compilarse de C ++ a ASM (código de máquina nativo) o cómo puede escribirlo en el ensamblador. Para proporcionar cualquier Secuencia de exclusión de consistencia, puede escribir MOV simple, por ejemplo, MOV reg, [addr] y MOV [addr], reg etc.

2. Consistencia Secuencial: Pero para proporcionar Consistencia Secuencial debe usar cercas implícitas ( LOCK ) o explícitas (L / S / MFENCE ) como se describe aquí: ¿Por qué GCC no usa LOAD (sin cerca) y STORE + SFENCE para Consistencia Secuencial?

  1. LOAD (sin valla) y STORE + MFENCE
  2. LOAD (sin valla) y LOCK XCHG
  3. MFENCE + LOAD y STORE (sin valla)
  4. LOCK XADD (0) y STORE (sin valla)

Por ejemplo, GCC usa 1, pero MSVC usa 2. (Pero debe saber que MSVS2012 tiene un error: ¿la semántica de `std :: memory_order_acquire` requiere instrucciones del procesador en x86 / x86_64? )

Luego, puede leer Herb Sutter, su enlace: https://onedrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&authkey=!AMtj_EflYn2507c

Excepcion a la regla:

Esta regla es verdadera para el acceso mediante el uso de MOV a la RAM convencional marcada de forma predeterminada como WB - Write Back. La memoria está marcando en la tabla de páginas , en cada PTE (página de tabla de entrada) para cada página (4 KB de memoria continua).

Pero hay algunas excepciones:

  1. Si ioremap_wc() memoria en la Tabla de páginas como Escritura combinada ( ioremap_wc() en POSIX), automáticamente proporcionamos solo Adquirir consistencia, y debemos actuar como en el párrafo siguiente.

  2. Ver respuesta a mi pregunta: https://.com/a/27302931/1558037

  • Las escrituras en la memoria no se reordenan con otras escrituras, con las siguientes excepciones :
    • escrituras ejecutadas con la instrucción CLFLUSH;
    • almacenes de transmisión (escrituras) ejecutados con las instrucciones de movimiento no temporal (MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS y MOVNTPD); y
    • operaciones de cuerda (ver Sección 8.2.4.1).

En ambos casos, 1 y 2, debe usar SFENCE adicional entre dos escrituras en la misma dirección, incluso si desea Acquire-Release Consistency, porque aquí automáticamente solo ofrece Acquire Consistency y usted debe hacer Release ( SFENCE ) usted mismo.

Responde tus dos preguntas:

A veces, al hacer una tienda, una CPU escribirá en su búfer de tienda en lugar de la memoria caché L1. Sin embargo, no entiendo los términos en los que una CPU hará esto.

Desde el punto de vista del usuario, el caché L1 y el Tampón de Tienda actúan de manera diferente. L1 rápido, pero Store-Buffer más rápido.

  • Store-Buffer: es una cola sencilla donde solo se almacena Written, y que no se puede reordenar: está hecha para aumentar el rendimiento y ocultar la latencia de acceso a la caché (L1 - 1ns, L2 - 3ns, L3 - 10ns) (CPU-Core piense que Write ha almacenado en la memoria caché y ejecuta el siguiente comando, pero al mismo tiempo sus escrituras solo se guardan en el búfer de la tienda y se guardarán en la memoria caché L1 / 2/3 más tarde), es decir, CPU-Core no necesita esperar cuando las Escrituras se hayan almacenado en la memoria caché.

  • Caché L1 / 2/3: parece una matriz asociada transparente (dirección - valor). Es rápido, pero no el más rápido, porque x86 proporciona automáticamente Consistencia-Liberación de Consistencia mediante el uso del protocolo coherente MESIF / MOESI . Se hace para una programación de hilos múltiples más simple, pero disminuye el rendimiento. (Verdaderamente, podemos usar algoritmos y estructuras de datos de Write Contentions Free sin utilizar el caché coherente, es decir, sin MESIF / MOESI, por ejemplo, a través de PCI Express ). Protocolos MESIF / MOESI funciona sobre QPI que conecta núcleos en CPU y núcleos entre diferentes CPU en sistemas multiprocesador ( ccNUMA ).

CPU2 puede desear cargar un valor que haya sido escrito en el almacenamiento intermedio de almacenamiento de CPU1. Según tengo entendido, el problema es que la CPU2 no puede ver el nuevo valor en el almacenamiento intermedio de almacenamiento de la CPU1.

Sí.

¿Por qué no puede el protocolo MESI simplemente incluir búferes de tienda de descarga como parte de su protocolo?

El protocolo MESI no puede incluir búferes de tienda de descarga como parte de su protocolo, porque:

  • Los protocolos MESI / MOESI / MESIF no están relacionados con el búfer de tienda y no lo conocen.
  • Vaciar automáticamente Buffer Store en cada Writes disminuiría el rendimiento y lo haría inútil.
  • La descarga manual de Buffer de Tienda en todos los CPU-Cores remotos (no sabemos en qué Core store-buffer contiene Write requerido) utilizando algún comando - disminuiría el rendimiento (en 8 CPUs x 15 Cores = 120 Cores al mismo tiempo Store -Buffer - esto es terrible)

Pero, manualmente, descargue Store Buffer en la CPU-Core actual: sí, puede hacerlo ejecutando el comando SFENCE . Puede usar SFENCE en dos casos:

  • Para proporcionar consistencia secuencial en la RAM con Write Back cacheable
  • Para proporcionar Adquirir-Liberar consistencia en excepciones de la regla : RAM con escritura combinada en caché, para escrituras ejecutadas con la instrucción CLFLUSH y para comandos SSE / AVX no temporales

Nota:

¿Necesitamos LFENCE en cualquier caso en x86 / x86_64? - la pregunta no siempre es clara: ¿Tiene algún sentido la instrucción LFENCE en los procesadores x86 / x86_64?

Otra plataforma:

Luego, puede leer como en teoría (para un procesador esférico in vacuo) con Store-Buffer e Invalidate-Queue, su enlace: http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf

Y cómo puede proporcionar consistencia secuencial en otras plataformas, no solo con L / S / MFENCE y LOCK sino también con LL/SC : http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html