performance x86 sse cpu-cache prefetch

performance - Las cargas no temporales y el prefetcher de hardware, ¿funcionan juntos?



x86 sse (4)

Esta pregunta me hizo leer un poco ... Mirando el manual de Intel para MOVNTDQA (usando una edición del 14 de septiembre), hay una declaración interesante:

Una implementación de procesador puede hacer uso de la sugerencia no temporal asociada con esta instrucción si la fuente de memoria es del tipo de memoria WC (combinación de escritura). Una implementación también puede hacer uso de la sugerencia no temporal asociada con esta instrucción si la fuente de memoria es del tipo de memoria WB (reescritura).

y luego

El tipo de memoria de la región que se lee puede anular la sugerencia no temporal, si la dirección de memoria especificada para la lectura no temporal no es una región de memoria WC.

Por lo tanto, parece que no hay garantía de que la sugerencia no temporal haga algo a menos que su tipo de memoria sea WC. Realmente no sé qué significa el comentario de tipo de memoria WB, tal vez algunos procesadores Intel le permiten usarlo para los beneficios de reducir la contaminación de caché, o tal vez quisieron mantener esta opción en el futuro (para que no comience a usar MOVNTDQA en WB mem y suponga que siempre se comportaría igual), pero está bastante claro que WC mem es el caso de uso real aquí. Desea que esta instrucción proporcione un almacenamiento intermedio a corto plazo para cosas que de otro modo serían completamente imposibles de almacenar en caché.

Ahora, por otro lado, mirando la descripción de prefetch *:

Se ignoran las captaciones previas de memoria no almacenable en caché o WC.

Eso cierra la historia: su pensamiento es absolutamente correcto, es probable que estos dos no sean intencionados y que no funcionen juntos, lo más probable es que uno de ellos sea ignorado.

Ok, pero ¿hay alguna posibilidad de que estos 2 realmente funcionen (si el procesador implementa cargas NT para la memoria WB)? Bueno, leyendo MOVNTDQA nuevamente, algo más llama la atención:

Cualquier línea con alias de tipo memoria en el caché será indagada y vaciada.

Ay. Por lo tanto, si de alguna manera logra precargar en su caché, es probable que degrade el rendimiento de cualquier carga de transmisión consecutiva, ya que primero tendría que eliminar la línea. No es un pensamiento bonito.

Al ejecutar una serie de llamadas _mm_stream_load_si128() ( MOVNTDQA ) desde ubicaciones de memoria consecutivas, ¿se activará el pre-captador de hardware o debería usar la captación previa de software explícita (con sugerencia de NTA) para obtener los beneficios de la captación previa mientras todavía evitando la contaminación del caché?

La razón por la que pregunto esto es porque sus objetivos me parecen contradictorios. Una carga de transmisión buscará datos sin pasar por el caché, mientras que el pretratador intenta recuperar datos de forma proactiva en el caché.

Al iterar secuencialmente una estructura de datos grande (los datos procesados ​​no se retocarán en mucho tiempo), tendría sentido para mí evitar contaminar la jerarquía de chache, pero no quiero incurrir en penalizaciones frecuentes de ~ 100 ciclos porque -fetcher está inactivo.

La arquitectura de destino es Intel SandyBridge


Recientemente hice algunas pruebas de los diversos sabores de prefetch previa mientras respondía a otra pregunta y mis hallazgos fueron:

Los resultados del uso de prefetchnta fueron consistentes con la siguiente implementación en el cliente Skylake:

  • prefetchnta carga valores en L1 y L3 pero no en L2 (de hecho, parece que la línea puede ser expulsada de L2 si ya está allí).
  • Parece cargar el valor "normalmente" en L1, pero de una manera más débil en L3, de modo que se desaloja más rápidamente (por ejemplo, solo de una sola manera en el conjunto, o con su bandera LRU establecida de modo que sea el próxima víctima).
  • prefetchnta , como todas las demás instrucciones de prefetch, usa una entrada LFB, por lo que realmente no ayudan a obtener paralelismo adicional: pero la sugerencia de NTA puede ser útil aquí para evitar la contaminación L2 y L3.

El manual de optimización actual (248966-038) afirma en algunos lugares que prefetchnta trae datos al L2, pero solo de una manera fuera del conjunto. Por ejemplo, en 7.6.2.1 Video Encoder :

La gestión de caché de captación previa implementada para el codificador de video reduce el tráfico de memoria. La reducción de la contaminación del caché de segundo nivel se garantiza al evitar que los datos de cuadros de video de un solo uso ingresen al caché de segundo nivel. El uso de una instrucción PREFETCH (PREFETCHNTA) no temporal lleva los datos a una sola forma del caché de segundo nivel, reduciendo así la contaminación del caché de segundo nivel.

Esto no es consistente con los resultados de mi prueba en Skylake, donde caminar sobre una región de 64 KiB con prefetchnta muestra un rendimiento casi exactamente consistente con la obtención de datos del L3 (~ 4 ciclos por carga, con un factor MLP de 10 y una latencia L3 de unos 40 ciclos):

Cycles ns 64-KiB parallel loads 1.00 0.39 64-KiB parallel prefetcht0 2.00 0.77 64-KiB parallel prefetcht1 1.21 0.47 64-KiB parallel prefetcht2 1.30 0.50 64-KiB parallel prefetchnta 3.96 1.53

Dado que el L2 en Skylake es de 4 vías, si los datos se cargaron de una manera, apenas debería permanecer en el caché L2 (una de las cuales cubre 64 KiB), pero los resultados anteriores indican que no.

Puede ejecutar estas pruebas en su propio hardware en Linux utilizando mi programa uarch-bench . Los resultados para sistemas antiguos serían particularmente interesantes.

Servidor Skylake (SKLX)

El comportamiento informado de prefetchnta en Skylake Server, que tiene una arquitectura de caché L3 different , es significativamente diferente del cliente de Skylake. En particular, el usuario Mysticial informa que las líneas obtenidas con prefetchnta no están disponibles en ningún nivel de caché y deben volverse a leer desde DRAM una vez que se desalojan de L1.

La explicación más probable es que nunca ingresaron a L3 como resultado de la prefetchnta ; esto es probable ya que en el servidor Skylake el L3 es un caché de víctimas compartido no inclusivo para los cachés L2 privados, por lo que las líneas que eluden el caché L2 usando Es probable que los prefetchnta nunca tengan la oportunidad de ingresar a la L3. Esto hace que prefetchnta tenga una función más pura: menos niveles de caché están contaminados por las solicitudes de prefetchnta , pero también más frágil: cualquier falla al leer una línea nta de L1 antes de ser desalojada significa otra vuelta completa a la memoria: la solicitud inicial activada por el prefetchnta Está totalmente desperdiciado.


Según la publicación de noviembre de 2011 de Patrick Fay (Intel): "En procesadores Intel recientes, prefetchnta trae una línea desde la memoria al caché de datos L1 (y no a los otros niveles de caché)". También dice que debe asegurarse de no realizar la captación previa demasiado tarde (la captación previa de HW ya la habrá llevado a todos los niveles), o demasiado pronto (desalojada cuando llegue allí).

Como se discutió en los comentarios sobre el OP, las CPU Intel actuales tienen un gran L3 compartido que incluye todas las cachés por núcleo. Esto significa que el tráfico de coherencia de caché solo tiene que verificar las etiquetas L3 para ver si una línea de caché podría modificarse en algún lugar en un L1 / L2 por núcleo.

IDK cómo conciliar la explicación de Pat Fay con mi comprensión de la coherencia de caché / jerarquía de caché. Pensé que si iba en L1, también tendría que ir en L3. ¿Quizás las etiquetas L1 tienen algún tipo de bandera para decir que esta línea está ordenada débilmente? Mi mejor conjetura es que estaba simplificando y diciendo L1 cuando en realidad solo va en buffers de relleno.

Esta guía de Intel sobre cómo trabajar con RAM de video habla sobre movimientos no temporales que utilizan buffers de carga / almacenamiento, en lugar de líneas de caché. (Tenga en cuenta que esto puede ser solo el caso de la memoria no almacenable en caché ). No menciona la captación previa . También es viejo, anterior a SandyBridge. Sin embargo, tiene esta jugosa cita:

Las instrucciones de carga ordinarias extraen datos de la memoria USWC en unidades del mismo tamaño que solicita la instrucción. Por el contrario, una instrucción de carga de transmisión como MOVNTDQA comúnmente extraerá una línea de datos de caché completa a un "buffer de relleno" especial en la CPU. Las cargas de transmisión subsiguientes leerían de ese búfer de relleno, incurriendo en mucho menos retraso.

Y luego, en otro párrafo, dice que las CPU típicas tienen 8 a 10 búferes de llenado. SnB / Haswell todavía tienen 10 por núcleo. . Nuevamente, tenga en cuenta que esto solo puede aplicarse a regiones de memoria no almacenables en caché.

movntdqa en la movntdqa WB ( movntdqa ) no está ordenada débilmente (consulte la sección de cargas de NT de la respuesta vinculada) , por lo que no se permite que esté "obsoleta". A diferencia de las tiendas NT, ni movntdqa ni prefetchnta cambian la semántica de ordenamiento de memoria de la memoria de movntdqa .

No he probado esta suposición , pero prefetchnta / movntdqa en una CPU Intel moderna podría cargar una línea de caché en L3 y L1, pero podría omitir L2 (porque L2 no incluye ni excluye L1). La sugerencia de NT podría tener efecto colocando la línea de caché en la posición LRU de su conjunto, donde es la siguiente línea que se desalojará. (La política de caché normal inserta nuevas líneas en la posición MRU, más lejos de ser desalojado. Consulte este artículo sobre la política L3 adaptativa de IvB para obtener más información sobre la política de inserción de caché ).

El rendimiento de captación previa en IvyBridge es solo uno por cada 43 ciclos, así que tenga cuidado de no captar demasiado si no desea que las captaciones previas ralenticen su código en IvB. Fuente: tablas insn de Agner Fog y guía de microarquitectura . Este es un error de rendimiento específico de IvB. En otros diseños, demasiada captación previa solo absorberá el rendimiento de UOP que podría haber sido instrucciones útiles (además del daño de captar direcciones inútiles).

Acerca de la captación previa de SW en general (no del tipo nt ): Linus Torvalds publicó sobre cómo raramente ayudan en el kernel de Linux y, a menudo, hacen más daño que bien . Aparentemente, la captación previa de un puntero NULL al final de una lista vinculada puede causar una desaceleración, porque intenta un relleno TLB.


Tanto MOVNTDQA (en la memoria WC) como PREFETCHNTA no afectan ni activan ninguno de los MOVNTDQA hardware de caché. La idea general de la sugerencia no temporal es evitar por completo la contaminación de la memoria caché o al menos minimizarla tanto como sea posible.

Solo hay un número muy pequeño (no documentado) de búferes llamados búferes de carga de transmisión (estos están separados de los búferes de relleno de línea y del caché L1) para mantener las líneas de caché recuperadas usando MOVNTDQA . Entonces, básicamente, necesitas usar lo que buscas casi de inmediato. Además, MOVNTDQA solo funciona en la memoria WC.

La instrucción PREFETCHNTA es perfecta para su escenario, pero debe descubrir cómo usarla correctamente en su código. Del manual de optimización de Intel Sección 7.1:

Si su algoritmo es de una sola pasada, use PREFETCHNTA. Si su algoritmo es de múltiples pasos, use PREFETCHT0.

La instrucción PREFETCHNTA ofrece los siguientes beneficios:

  • Busca la línea de caché particular que contiene la dirección especificada en al menos el caché L3 y / o niveles potencialmente más altos de la jerarquía de caché (consulte la respuesta de Bee y Peter y la Sección 7.3.2). En cada nivel de caché que se almacena en caché, podría / debería / más probablemente ser considerado el primero en ser desalojado en caso de que sea necesario desalojar una línea del conjunto. En una implementación de un algoritmo de paso único (como calcular el promedio de una gran variedad de números) que se mejora con PREFETCHNTA , las líneas de caché captadas previamente se pueden colocar en el mismo bloque que aquellas líneas que también se captaron PREFETCHNTA usando PREFETCHNTA . Por lo tanto, incluso si la cantidad total de datos que se obtiene es masiva, solo una forma de la caché completa se verá afectada. Los datos que residen en las otras formas permanecerán en caché y estarán disponibles después de que el algoritmo termine. Pero esta es una espada de doble filo. Si dos instrucciones PREFETCHNTA están demasiado cerca una de la otra y si las direcciones especificadas se asignan al mismo conjunto de caché, solo una sobrevivirá.
  • Las líneas de caché captadas PREFETCHNTA con PREFETCHNTA se mantienen coherentes como cualquier otra línea de caché que utiliza el mismo mecanismo de coherencia de hardware.
  • Funciona en los tipos de memoria WB, WC y WT. Lo más probable es que sus datos estén almacenados en la memoria WB
  • Como dije antes, no activa la captación previa de hardware. Es por esta razón por la que también se puede utilizar para mejorar el rendimiento de los patrones irregulares de acceso a la memoria recomendados por Intel.

Es posible que el subproceso que ejecuta PREFETCHNTA no pueda beneficiarse de manera efectiva dependiendo del comportamiento de cualquier otro subproceso en ejecución en el mismo núcleo físico, en otros núcleos físicos del mismo procesador o en los núcleos de otros procesadores que comparten el mismo dominio de coherencia . Las técnicas como la fijación, el aumento de prioridad, el particionamiento de caché basado en CAT y la desactivación de hyperthreading pueden ayudar a que ese hilo se ejecute de manera eficiente. Tenga en cuenta también que PREFETCHNTA se clasifica como una carga especulativa y, por lo tanto, es concurrente con las tres instrucciones de valla.