open link depurer depurador debugger contenido cache caching assembly 64bit bandwidth prefetch

caching - link - opengraph whatsapp



¿La captación previa de software asigna un búfer de relleno de línea(LFB)? (2)

En primer lugar, una corrección menor: lea la guía de optimización , y observará que algunos prefetchers de HW pertenecen a la caché L2, y como tales no están limitados por el número de búferes de relleno sino por la contraparte L2.

El "captador previo espacial" (la línea de 64B colocada que quiere decir, completar a trozos de 128B) es uno de ellos, por lo que en teoría si obtiene todas las demás líneas podrá obtener un mayor ancho de banda (algunos buscadores previos de DCU podrían intentarlo). "llene los huecos para usted", pero en teoría deberían tener una prioridad más baja para que funcione.

Sin embargo, el prefetcher del "rey" es el otro tipo, el "streamer L2". La sección 2.1.5.4 dice:

Streamer: este prefetcher supervisa las solicitudes de lectura de la memoria caché L1 para las secuencias de direcciones ascendentes y descendentes. Las solicitudes de lectura supervisadas incluyen las solicitudes de L1 DCache iniciadas por las operaciones de carga y almacenamiento y por los buscadores previos de hardware, y las solicitudes de L1 ICache para obtener el código. Cuando se detecta un flujo de solicitudes hacia delante o hacia atrás, las líneas de caché anticipadas se toman previamente. Las líneas de caché prefijadas deben estar en la misma página 4K

La parte importante es -

El transmisor puede emitir dos solicitudes de captación previa en cada búsqueda L2. El streamer puede correr hasta 20 líneas por delante de los requisitos de carga.

Esta relación 2: 1 significa que para una secuencia de accesos reconocida por este prefetcher, siempre se ejecutará por delante de sus accesos. Es cierto que no verás estas líneas en tu L1 automáticamente, pero sí significa que si todo funciona bien, siempre debes obtener latencia de L2 para ellas (una vez que la secuencia de captación previa tuvo tiempo suficiente para avanzar y mitigar L3 / memoria). latencias). Es posible que solo tenga 10 LFB, pero como anotó en su cálculo: cuanto más corta sea la latencia de acceso, más rápido podrá reemplazarlos cuanto mayor sea el ancho de banda que pueda alcanzar. Básicamente, se trata de separar la latencia L1 <-- mem en flujos paralelos de L1 <-- L2 y L2 <-- mem .

En cuanto a la pregunta en su título, es lógico pensar que los pasos previos que intentan llenar el L1 requieren un búfer de relleno de línea para mantener los datos recuperados para ese nivel. Esto probablemente debería incluir todos los prefetches L1. En cuanto a los prefetches de SW, la sección 7.4.3 dice:

Hay casos en que un PREFETCH no realizará la captación previa de datos. Éstos incluyen:

  • PREFETCH provoca un error DTLB (Data Translation Lookaside Buffer). Esto se aplica a los procesadores Pentium 4 con la firma CPUID correspondiente a la familia 15, modelo 0, 1 o 2. PREFETCH resuelve los fallos de DTLB y obtiene datos en los procesadores Pentium 4 con la firma CPUID correspondiente a la familia 15, modelo 3.
  • Un acceso a la dirección especificada que causa un error / excepción.
  • Si el subsistema de memoria se queda sin buffers de solicitud entre el caché de primer nivel y el caché de segundo nivel.

...

Por lo tanto, asumo que tiene razón y que los prefetches SW no son una forma de aumentar artificialmente su número de solicitudes pendientes. Sin embargo, la misma explicación se aplica aquí también: si sabe cómo utilizar la captación previa de SW para acceder a sus líneas con suficiente antelación, puede mitigar parte de la latencia de acceso y aumentar su BW efectivo. Sin embargo, esto no funcionará para transmisiones largas por dos razones: 1) su capacidad de caché es limitada (incluso si la captación previa es temporal, como t0), y 2) aún debe pagar la latencia total de L1 -> mem para cada captación previa, por lo que solo está moviendo su estrés un poco más adelante: si la manipulación de sus datos es más rápida que el acceso a la memoria, finalmente se pondrá al día con la captación previa de SW. Así que esto solo funciona si puede obtener previamente todo lo que necesita con suficiente antelación y mantenerlo allí.

Me he dado cuenta de que la Ley de Little limita la rapidez con la que se pueden transferir los datos a una latencia determinada y con un nivel de concurrencia determinado. Si desea transferir algo más rápido, necesita transferencias más grandes, más transferencias "en vuelo" o una latencia más baja. Para el caso de la lectura de la RAM, la concurrencia está limitada por el número de búferes de relleno de línea.

Se asigna un búfer de relleno de línea cuando una carga pierde la memoria caché L1. Los chips modernos de Intel (Nehalem, Sandy Bridge, Ivy Bridge, Haswell) tienen 10 LFB por núcleo y, por lo tanto, están limitados a 10 fallas de caché pendientes por núcleo. Si la latencia de la RAM es de 70 ns (plausible), y cada transferencia es de 128 bytes (línea de caché de 64B más su hardware con doble pinchazo), esto limita el ancho de banda por núcleo a: 10 * 128B / 75 ns = ~ 16 GB / s. Los puntos de referencia como Stream solo hilo confirman que esto es razonablemente preciso.

La forma obvia de reducir la latencia sería obtener los datos deseados con instrucciones x64 como PREFETCHT0, PREFETCHT1, PREFETCHT2 o PREFETCHNTA para que no tenga que leerse desde la RAM. Pero no he podido acelerar nada con su uso. El problema parece ser que las instrucciones __mm_prefetch () consumen LFB, por lo que también están sujetas a los mismos límites. Los elementos previos de hardware no tocan los LFB, pero tampoco cruzan los límites de la página.

Pero no puedo encontrar nada de esto documentado en ninguna parte. Lo más cercano que he encontrado es un article 15 años que dice que las menciones previas al Pentium III utilizan los tampones de relleno de línea. Me preocupa que las cosas hayan cambiado desde entonces. Y como creo que los LFB están asociados con el caché L1, no estoy seguro de por qué un prefetch a L2 o L3 los consumiría. Y, sin embargo, las velocidades que mido son consistentes con este caso.

Entonces: ¿hay alguna manera de iniciar una búsqueda desde una nueva ubicación en la memoria sin utilizar uno de esos 10 búferes de relleno de línea, logrando así un mayor ancho de banda al rodear la Ley de Little?


Según mis pruebas, todos los tipos de instrucciones de captación previa consumen buffers de relleno de línea en las recientes CPU de Intel .

En particular, agregué algunas pruebas de carga y captación previa a uarch-bench , que usan cargas de grandes zancadas en buffers de varios tamaños. Aquí están los resultados típicos en mi Skylake i7-6700HQ:

Benchmark Cycles Nanos 16-KiB parallel loads 0.50 0.19 16-KiB parallel prefetcht0 0.50 0.19 16-KiB parallel prefetcht1 1.15 0.44 16-KiB parallel prefetcht2 1.24 0.48 16-KiB parallel prefetchtnta 0.50 0.19 32-KiB parallel loads 0.50 0.19 32-KiB parallel prefetcht0 0.50 0.19 32-KiB parallel prefetcht1 1.28 0.49 32-KiB parallel prefetcht2 1.28 0.49 32-KiB parallel prefetchtnta 0.50 0.19 128-KiB parallel loads 1.00 0.39 128-KiB parallel prefetcht0 2.00 0.77 128-KiB parallel prefetcht1 1.31 0.50 128-KiB parallel prefetcht2 1.31 0.50 128-KiB parallel prefetchtnta 4.10 1.58 256-KiB parallel loads 1.00 0.39 256-KiB parallel prefetcht0 2.00 0.77 256-KiB parallel prefetcht1 1.31 0.50 256-KiB parallel prefetcht2 1.31 0.50 256-KiB parallel prefetchtnta 4.10 1.58 512-KiB parallel loads 4.09 1.58 512-KiB parallel prefetcht0 4.12 1.59 512-KiB parallel prefetcht1 3.80 1.46 512-KiB parallel prefetcht2 3.80 1.46 512-KiB parallel prefetchtnta 4.10 1.58 2048-KiB parallel loads 4.09 1.58 2048-KiB parallel prefetcht0 4.12 1.59 2048-KiB parallel prefetcht1 3.80 1.46 2048-KiB parallel prefetcht2 3.80 1.46 2048-KiB parallel prefetchtnta 16.54 6.38

La clave a tener en cuenta es que ninguna de las técnicas de captación previa es mucho más rápida que las cargas en cualquier tamaño de búfer. Si alguna instrucción de captación previa no utilizara el LFB, esperaríamos que fuera muy rápido para un punto de referencia que se ajuste al nivel de caché que prefiera. Por ejemplo, prefetcht1 trae líneas a la L2, por lo que para la prueba de 128 KiB podríamos esperar que sea más rápida que la variante de carga si no usa LFB.

De manera más concluyente, podemos examinar el contador l1d_pend_miss.fb_full , cuya descripción es:

Número de veces que una solicitud necesitó una entrada de FB (tampón de relleno) pero no había ninguna entrada disponible para ella. Una solicitud incluye demandas que se pueden almacenar en caché / que no se pueden almacenar en caché que son instrucciones de carga, almacenamiento o búsqueda previa de SW .

La descripción ya indica que las prefetches SW necesitan entradas LFB y las pruebas lo confirmaron: para todos los tipos de captación previa, esta cifra fue muy alta para cualquier prueba donde la concurrencia fue un factor limitante. Por ejemplo, para la prueba prefetcht1 512-KiB:

Performance counter stats for ''./uarch-bench --test-name 512-KiB parallel prefetcht1'': 38,345,242 branches 1,074,657,384 cycles 284,646,019 mem_inst_retired.all_loads 1,677,347,358 l1d_pend_miss.fb_full

El valor fb_full es más que el número de ciclos, lo que significa que el LFB estuvo lleno casi todo el tiempo (puede ser más que el número de ciclos, ya que hasta dos cargas pueden querer un LFB por ciclo). Esta carga de trabajo es solo de prefetches, por lo que no hay nada para llenar los LFB, excepto el prefetch.

Los resultados de esta prueba también contraen el comportamiento declarado en la sección del manual citado por Leeor:

Hay casos en que un PREFETCH no realizará la captación previa de datos. Éstos incluyen:

  • ...
  • Si el subsistema de memoria se queda sin buffers de solicitud entre el caché de primer nivel y el caché de segundo nivel.

Claramente, este no es el caso aquí: las solicitudes de captación previa no se eliminan cuando los LFB se llenan, sino que se bloquean como una carga normal hasta que los recursos están disponibles (esto no es un comportamiento irrazonable: si solicitó una captación previa de software, probablemente desee para conseguirlo, tal vez incluso si eso significa estancamiento).

También observamos los siguientes comportamientos interesantes:

  • Parece que hay una pequeña diferencia entre prefetcht1 y prefetcht2 ya que reportan un rendimiento diferente para la prueba de 16 KiB (la diferencia varía, pero es diferente), pero si repite la prueba verá que es más probable que esto ocurra. variación de ejecución a ejecución ya que esos valores particulares son algo inestables (la mayoría de los otros valores son muy estables).
  • Para las pruebas contenidas en L2, podemos mantener 1 carga por ciclo, pero solo una prefetcht0 previa prefetcht0. Esto es algo extraño porque prefetcht0 debería ser muy similar a una carga (y puede emitir 2 por ciclo en los casos de L1).
  • Aunque el L2 tiene una latencia de ~ 12 ciclos, podemos ocultar completamente el LFB de latencia con solo 10 LFB: obtenemos 1.0 ciclos por carga (limitado por el rendimiento de L2), no 12/10 12 / 10 == 1.2 ciclos por carga que '' Espere (el mejor de los casos) si LFB fuera el hecho limitante (y los valores muy bajos para fb_full confirman). Esto probablemente se deba a que la latencia de 12 ciclos es la latencia de carga completa hasta el núcleo de ejecución, que incluye también varios ciclos de latencia adicional (por ejemplo, la latencia L1 es de 4-5 ciclos), por lo que el tiempo real empleado en El LFB tiene menos de 10 ciclos.
  • Para las pruebas de L3, vemos valores de 3.8-4.1 ciclos, muy cerca de los esperados 42/10 = 4.2 ciclos basados ​​en la latencia de carga a usar de L3. Así que definitivamente estamos limitados por los 10 LFB cuando alcanzamos la L3. Aquí prefetcht1 y prefetcht2 son consistentemente 0.3 ciclos más rápidos que las cargas o prefetcht0 . Dados los 10 LFB, eso equivale a 3 ciclos menos de ocupación, más o menos explicados por la búsqueda previa que se detiene en L2 en lugar de ir hasta L1.
  • prefetchtnta generalmente tiene un rendimiento mucho menor que los otros fuera de L1. Esto probablemente significa que prefetchtnta realidad está haciendo lo que se supone que debe hacer y parece traer líneas a L1, no a L2, y solo "débilmente" a L3. Por lo tanto, para las pruebas contenidas en L2 tiene un rendimiento limitado por la concurrencia como si estuviera golpeando el caché L3, y para el caso 2048-KiB (1/3 del tamaño del caché L3) tiene el rendimiento de golpear la memoria principal. prefetchnta limita la contaminación de caché L3 (a algo así como solo una forma por conjunto) , por lo que parece que estamos recibiendo desalojos.

¿Podría ser diferente?

Aquí hay una respuesta anterior que escribí antes de probar, especulando sobre cómo podría funcionar:

En general, esperaría que cualquier captación previa que resulte en que los datos terminen en L1 consuma un búfer de relleno de línea, ya que creo que la única ruta entre L1 y el resto de la jerarquía de memoria es la LFB 1 . Por lo tanto, los prefetches SW y HW que apuntan al L1 probablemente utilicen ambos LFB.

Sin embargo, esto deja abierta la probabilidad de que los prefetches que apuntan a niveles L2 o superiores no consuman LFB. Para el caso de la captación previa de hardware, estoy seguro de que este es el caso: puede encontrar muchas referencias que explican que la captación previa de hardware es un mecanismo para obtener más paralelismo de memoria más allá del máximo de 10 que ofrece el LFB. Además, no parece que los prefetchers L2 pudieran usar los LFB si quisieran: viven en / cerca del L2 y emiten solicitudes a niveles más altos, probablemente utilizando el supercoque y no necesitarían los LFB.

Eso deja al software prefetch que apunta al L2 (o superior), como prefetcht1 y prefetcht2 2 . A diferencia de las solicitudes generadas por el L2, estas comienzan en el núcleo, por lo que necesitan alguna forma de salir del núcleo, y esto podría ser a través del LFB. De la guía de optimización de Intel tenga la siguiente cita interesante (énfasis mío):

En general, la búsqueda previa de software en el L2 mostrará más beneficios que los prefetches L1. Una captación previa de software en L1 consumirá recursos de hardware críticos (búfer de relleno) hasta que se complete el llenado de la cacheline. Una búsqueda previa de software en L2 no contiene esos recursos , y es menos probable que tenga un impacto negativo en el rendimiento. Si utiliza las comprobaciones previas del software L1, es mejor si la captación previa del software es atendida por aciertos en la memoria caché L2, por lo que se minimiza el tiempo que se retienen los recursos de hardware.

Esto parecería indicar que las pruebas previas de software no consumen LFB, pero esta cita solo se aplica a la arquitectura de Knights Landing, y no puedo encontrar un lenguaje similar para ninguna de las arquitecturas más tradicionales. Parece que el diseño de caché de Knights Landing es significativamente diferente (o la cita es incorrecta).

1 De hecho, creo que incluso las tiendas no temporales usan los LFB para salir del núcleo de ejecución, pero su tiempo de ocupación es corto porque tan pronto como llegan a la L2 pueden ingresar a la supercola (sin ir realmente a la L2 ) y luego libere su LFB asociado.

2 Creo que ambos apuntan al L2 en Intel reciente, pero esto tampoco está claro. ¿Quizás la sugerencia de t2 realmente apunta a LLC en algunos uarchs?