Me pregunto cuánto de lo que todo programador debe saber sobre la memoria de 2007 de Ulrich Drepper sigue siendo válido. Además, no pude encontrar una versión más nueva que la 1.0 o una errata.
164
Me pregunto cuánto de lo que todo programador debe saber sobre la memoria de 2007 de Ulrich Drepper sigue siendo válido. Además, no pude encontrar una versión más nueva que la 1.0 o una errata.
Respuestas:
Hasta donde recuerdo, el contenido de Drepper describe conceptos fundamentales sobre la memoria: cómo funciona la memoria caché de la CPU, qué es la memoria física y virtual y cómo el kernel de Linux maneja ese zoológico. Probablemente hay referencias API obsoletas en algunos ejemplos, pero no importa; eso no afectará la relevancia de los conceptos fundamentales.
Por lo tanto, cualquier libro o artículo que describa algo fundamental no puede llamarse obsoleto. Definitivamente vale la pena leer lo que todo programador debe saber sobre la memoria, pero, bueno, no creo que sea para "todos los programadores". Es más adecuado para el sistema / incrustado / kernel.
fuente
La guía en formato PDF está en https://www.akkadia.org/drepper/cpumemory.pdf .
En general, sigue siendo excelente y muy recomendable (por mí y por otros expertos en ajuste de rendimiento). Sería genial si Ulrich (o cualquier otra persona) escribiera una actualización de 2017, pero eso sería mucho trabajo (por ejemplo, volver a ejecutar los puntos de referencia). Consulte también otros enlaces de optimización de rendimiento x86 y SSE / asm (y C / C ++) en elx86 etiqueta wiki . (El artículo de Ulrich no es específico para x86, pero la mayoría (todos) de sus puntos de referencia están en hardware x86).
Los detalles de hardware de bajo nivel sobre cómo funcionan la DRAM y las memorias caché todavía se aplican . DDR4 utiliza los mismos comandos que se describen para DDR1 / DDR2 (ráfaga de lectura / escritura). Las mejoras DDR3 / 4 no son cambios fundamentales. AFAIK, todas las cosas independientes del arco todavía se aplican en general, por ejemplo, a AArch64 / ARM32.
Consulte también la sección Plataformas enlazadas a la latencia de esta respuesta para obtener detalles importantes sobre el efecto de la memoria / latencia L3 en el ancho de banda de un solo hilo:
bandwidth <= max_concurrency / latency
y este es en realidad el principal cuello de botella para el ancho de banda de un solo hilo en una CPU moderna de muchos núcleos como un Xeon . Pero un escritorio Skylake de cuatro núcleos puede estar cerca de maximizar el ancho de banda DRAM con un solo hilo. Ese enlace tiene información muy buena sobre las tiendas NT frente a las tiendas normales en x86. ¿Por qué Skylake es mucho mejor que Broadwell-E para el rendimiento de memoria de un solo subproceso? Es un resumen.Por lo tanto, la sugerencia de Ulrich en 6.5.8 Utilizando todo el ancho de banda sobre el uso de la memoria remota en otros nodos NUMA, así como en el suyo, es contraproducente en el hardware moderno donde los controladores de memoria tienen más ancho de banda de lo que puede usar un solo núcleo. Bueno, posiblemente pueda imaginar una situación en la que haya un beneficio neto al ejecutar múltiples subprocesos que consumen mucha memoria en el mismo nodo NUMA para la comunicación entre subprocesos de baja latencia, pero hacer que usen memoria remota para cosas de alto ancho de banda no sensibles a la latencia. Pero esto es bastante oscuro, normalmente solo divide los hilos entre nodos NUMA y pídales que usen la memoria local. El ancho de banda por núcleo es sensible a la latencia debido a los límites máximos de concurrencia (ver más abajo), pero todos los núcleos en un zócalo generalmente pueden más que saturar los controladores de memoria en ese zócalo.
(generalmente) No utilice la captación previa de software
Una cosa importante que ha cambiado es que la captación previa de hardware es mucho mejor que en el Pentium 4 y puede reconocer patrones de acceso progresivo hasta un paso bastante grande y múltiples transmisiones a la vez (por ejemplo, una hacia adelante / hacia atrás por página de 4k). El manual de optimización de Intel describe algunos detalles de los captadores previos HW en varios niveles de caché para su microarquitectura de la familia Sandybridge. Ivybridge y versiones posteriores tienen una captación previa de hardware en la página siguiente, en lugar de esperar a que se pierda un caché en la nueva página para activar un inicio rápido. Supongo que AMD tiene algunas cosas similares en su manual de optimización. Tenga en cuenta que el manual de Intel también está lleno de consejos antiguos, algunos de los cuales solo son buenos para P4. Las secciones específicas de Sandybridge son, por supuesto, precisas para SnB, pero por ejemplola deslaminación de los uops micro fusionados cambió en HSW y el manual no lo menciona .
El consejo habitual en estos días es eliminar todas las captaciones previas de SW del código anterior , y solo considere volver a colocarlo si el perfil muestra errores de caché (y no está saturando el ancho de banda de la memoria). Obtener previamente ambos lados del siguiente paso de una búsqueda binaria aún puede ayudar. Por ejemplo, una vez que decida qué elemento mirar a continuación, busque previamente los elementos 1/4 y 3/4 para que puedan cargarse en paralelo con la carga / comprobación del centro.
La sugerencia de usar un subproceso de captación previa separado (6.3.4) es totalmente obsoleto , creo, y solo fue bueno en Pentium 4. P4 tenía hyperthreading (2 núcleos lógicos que comparten un núcleo físico), pero no suficiente caché de rastreo (y / o recursos de ejecución fuera de orden) para obtener rendimiento ejecutando dos subprocesos de cálculo completos en el mismo núcleo. Pero las CPU modernas (Sandybridge-family y Ryzen) son mucho más robustas y deberían ejecutar un hilo real o no usar hyperthreading (deje el otro núcleo lógico inactivo para que el hilo solo tenga todos los recursos en lugar de particionar el ROB).
La captación previa de software siempre ha sido "frágil" : los números de ajuste mágico correctos para obtener una aceleración dependen de los detalles del hardware y quizás de la carga del sistema. Demasiado temprano y es desalojado antes de la carga de la demanda. Demasiado tarde y no ayuda. Este artículo de blog muestra gráficos de código + para un experimento interesante en el uso de captación previa de SW en Haswell para captar previamente la parte no secuencial de un problema. Consulte también ¿Cómo usar correctamente las instrucciones de captación previa? . La captación previa de NT es interesante, pero aún más frágil porque un desalojo temprano de L1 significa que tiene que ir hasta L3 o DRAM, no solo L2. Si necesita hasta la última caída de rendimiento, y puede sintonizar una máquina específica, vale la pena mirar la captación previa de SW para acceso secuencial, perotodavía puede ser una ralentización si tiene suficiente trabajo de ALU para hacer mientras se acerca al cuello de botella en la memoria.
El tamaño de la línea de caché sigue siendo de 64 bytes. (El ancho de banda de lectura / escritura de L1D es muy alto, y las CPU modernas pueden hacer 2 cargas de vectores por reloj + 1 almacenamiento de vectores si todo golpea en L1D. Consulte ¿Cómo puede ser tan rápido el caché? ). Con AVX512, tamaño de línea = ancho de vector, para que pueda cargar / almacenar una línea de caché completa en una sola instrucción. Por lo tanto, cada carga / almacén desalineado cruza un límite de línea de caché, en lugar de todos los demás para 256b AVX1 / AVX2, que a menudo no ralentiza el bucle sobre una matriz que no estaba en L1D.
Las instrucciones de carga no alineadas tienen penalización cero si la dirección está alineada en tiempo de ejecución, pero los compiladores (especialmente gcc) hacen un mejor código al autovectorizar si conocen alguna garantía de alineación. Las operaciones en realidad no alineadas son generalmente rápidas, pero las divisiones de página todavía duelen (mucho menos en Skylake, sin embargo; solo ~ 11 ciclos adicionales de latencia frente a 100, pero sigue siendo una penalización de rendimiento).
Como Ulrich predijo, todos los sistemas de múltiples sockets son NUMA en estos días: los controladores de memoria integrados son estándar, es decir, no hay un Northbridge externo. Pero SMP ya no significa multi-socket, porque las CPU multi-core están muy extendidas. Las CPU Intel de Nehalem a Skylake han utilizado un gran caché L3 inclusivo como respaldo para la coherencia entre núcleos. Las CPU AMD son diferentes, pero no soy tan claro en los detalles.
Skylake-X (AVX512) ya no tiene un L3 inclusivo, pero creo que todavía hay un directorio de etiquetas que le permite verificar qué está almacenado en caché en cualquier lugar del chip (y, de ser así, dónde) sin transmitir snoops a todos los núcleos. SKX usa una malla en lugar de un bus de anillo , con una latencia generalmente peor que los Xeons de muchos núcleos anteriores, desafortunadamente.
Básicamente, todos los consejos sobre la optimización de la ubicación de la memoria todavía se aplican, solo varían los detalles de lo que sucede exactamente cuando no se pueden evitar errores de caché o contenciones.
6.4.2 Operaciones atómicas : el punto de referencia que muestra un bucle de reintento de CAS 4 veces peor que el arbitrado por hardware
lock add
probablemente todavía refleja un caso de contención máxima . Pero en programas reales de subprocesos múltiples, la sincronización se mantiene al mínimo (porque es costosa), por lo que la contención es baja y un bucle de reintento de CAS generalmente tiene éxito sin tener que volver a intentarlo.C ++ 11
std::atomic
fetch_add
se compilará en alock add
(olock xadd
si se usa el valor de retorno), pero un algoritmo que usa CAS para hacer algo que no se puede hacer con unalock
instrucción ed generalmente no es un desastre. Use C ++ 11std::atomic
o C11 enstdatomic
lugar de las__sync
incorporaciones heredadas de gcc o las__atomic
incorporaciones más nuevas , a menos que desee combinar el acceso atómico y no atómico a la misma ubicación ...8.1 DWCAS (
cmpxchg16b
) : puede convencer a gcc para que lo emita , pero si desea cargas eficientes de solo la mitad del objeto, necesitaunion
hacks feos : ¿cómo puedo implementar el contador ABA con c ++ 11 CAS? . (No confunda DWCAS con DCAS de 2 ubicaciones de memoria separadas . La emulación atómica sin bloqueo de DCAS no es posible con DWCAS, pero la memoria transaccional (como x86 TSX) lo hace posible).8.2.4 memoria transaccional : después de un par de inicios falsos (liberados y luego deshabilitados por una actualización de microcódigo debido a un error que rara vez se activa), Intel tiene memoria transaccional en funcionamiento en Broadwell y todas las CPU Skylake. El diseño sigue siendo lo que David Kanter describió para Haswell . Hay una forma de elusión de bloqueo para usarlo para acelerar el código que usa (y puede recurrir a) un bloqueo normal (especialmente con un solo bloqueo para todos los elementos de un contenedor, por lo que múltiples hilos en la misma sección crítica a menudo no chocan) ), o para escribir código que sepa sobre transacciones directamente.
7.5 Grandes páginas : las grandes páginas transparentes anónimas funcionan bien en Linux sin tener que usar manualmente hugetlbfs. Haga asignaciones> = 2MiB con una alineación de 2MiB (por ejemplo
posix_memalign
, o unaaligned_alloc
que no imponga el estúpido requisito de ISO C ++ 17 para fallar cuandosize % alignment != 0
).Una asignación anónima alineada con 2MiB utilizará enormes páginas por defecto. Algunas cargas de trabajo (por ejemplo, que siguen usando asignaciones grandes durante un tiempo después de hacerlas) pueden beneficiarse
echo always >/sys/kernel/mm/transparent_hugepage/defrag
al hacer que el núcleo desfragmente la memoria física siempre que sea necesario, en lugar de retroceder a 4k páginas. (Ver los documentos del kernel ). Alternativamente, usemadvise(MADV_HUGEPAGE)
después de hacer grandes asignaciones (preferiblemente aún con una alineación de 2MiB).Apéndice B: Perfil : Linux se
perf
ha reemplazado en su mayoríaoprofile
. Para eventos detallados específicos de ciertas microarquitecturas, use elocperf.py
contenedor . p.ejPara ver algunos ejemplos de su uso, consulte ¿Puede el MOV de x86 ser realmente "gratis"? ¿Por qué no puedo reproducir esto en absoluto? .
fuente
Desde mi rápido vistazo, parece bastante preciso. Una cosa a notar es la porción en la diferencia entre controladores de memoria "integrados" y "externos". Desde el lanzamiento de la línea i7, las CPU Intel están integradas, y AMD ha estado utilizando controladores de memoria integrados desde que se lanzaron los chips AMD64.
Desde que se escribió este artículo, no ha cambiado mucho, las velocidades han aumentado, los controladores de memoria se han vuelto mucho más inteligentes (el i7 retrasará las escrituras en la RAM hasta que parezca que se cometen los cambios), pero no ha cambiado mucho. . Al menos no de ninguna manera que le interese a un desarrollador de software.
fuente