Fallas de página y almacenamiento de alto rendimiento.

Al analizar el rendimiento de un sistema de almacenamiento distribuido, hay muchas cosas a considerar. Las características de sus algoritmos (tanto locales como distribuidos), la topología de la red, las capacidades de rendimiento y latencia de sus medios de almacenamiento subyacentes, y muchos más. Pero hay un detalle que es fácil de pasar por alto: qué sucede cuando su programa necesita pedir memoria al sistema operativo.

En este post presentaré un par de anécdotas posiblemente divertidas sobre los errores de página y su impacto en el rendimiento.

¿Qué es un error de página?

Una falla de página es una especie de excepción en los sistemas de memoria virtual: ocurre cuando un proceso intenta acceder a una página de memoria leyendo o escribiendo en ella, y esa página no está asignada a un fragmento físico de bits en la RAM. (También ocurre una falla si un proceso accede a la memoria no está permitido, pero ese no es el tipo que nos interesa aquí). Las fallas de página son una parte perfectamente normal de la vida en la mayoría de los sistemas operativos, cuando un proceso mapea la memoria en su espacio de direcciones, el sistema operativo a menudo no asigna físicamente esa memoria hasta que se utiliza. Sin embargo, las fallas de página no son benignas para el rendimiento, ya que aumentan la latencia de ciertos accesos a la memoria mientras el sistema operativo actualiza sus tablas de páginas.

Una regresión del rendimiento del kernel de Linux

Hace un tiempo, nuestra línea híbrida de la serie C ejecutaba la versión 4.8 del Kernel de Linux. Esto no era ideal, ya que no era una versión del kernel de LTS y ya no era compatible con la pila de habilitación de LTS de Ubuntu, lo que significaba que corríamos el riesgo de quedarnos atrás de las últimas correcciones de errores y actualizaciones de seguridad. En ese momento, decidimos probar el kernel 4.13 más reciente, y cuando lo hicimos, las pruebas de rendimiento mostraron que el rendimiento se redujo hasta en un 25 por ciento.

Esa es una regresión bastante impactante, así que cavamos. Como lo único que había cambiado era el kernel, comencé observando las métricas del sistema usando la utilidad SAR. sysstat suite de monitoreo de rendimiento de Linux.

Para ayudar a reducir la búsqueda, una técnica que me gusta emplear es realizar una serie de pruebas de rendimiento de las compilaciones "buenas" y "malas", recopilar varias muestras de cada métrica que el SAR puede registrar y luego comparar las dos colecciones de métricas usando una prueba t de 2-muestra independiente, específicamente Las varianzas desiguales de Welch t-test. Esta es una prueba diseñada para evaluar la hipótesis de que dos poblaciones tienen medios iguales y está disponible de manera conveniente en la biblioteca de scipy.

Voy a profundizar más en esta técnica en una publicación futura, pero la esencia de esto es: ingerir las muestras de SAR en dos marcos de datos, realice la prueba t, descarte todas las filas con un valor p superior a algún umbral (generalmente elijo 5%) y ordene las restantes por estadística t (orden ascendente si las entradas fueron (buenas, malas) - esto enfatiza los resultados que tuvieron una correlación negativa con el desempeño, y que a menudo son interesantes). En este caso, el resultado se parece a esto:

Bueno, el título de este artículo mencionó fallas de página. Y aquí, parecía que ir a 4.13 había más que duplicado la tasa de errores de página para este punto de referencia. Entonces, a continuación, saqué la práctica herramienta de perf, que usé para registrar todos los eventos de mmap, munmap y error de página en el kernel en nombre del demonio del sistema de archivos de Qumulo durante treinta segundos:

$ perf record \ -e "syscalls: sys_enter_m * map" \ -e "excepciones: page_fault_ *" \ -p `pidof qfsd` sleep 30

Los eventos registrados contienen una gran cantidad de información útil, incluida la hora y la dirección en la que se produjo cada asignación o falla, el tipo de falla, etc. Utilicé un script de perf y algunos trabajos seductores para transformar los datos en CSV para un análisis más fácil

$ perf script | grep page_fault_ \ | sed -e 's /.* \ [\ (. * \) \] \ ([^:] \ + \). * dirección = 0x \ ([^] \ + \) f. * código_error = 0x \ ( . * \) / \ 1, \ 2, \ 3, \ 4 / '> faults.csv $ perf script | grep mmap \ | sed -e 's /.* \ [\ (. * \) \] \ ([^:] \ + \):. * addr: 0x \ ([^,] \ + \). * / \ 1, \ 2, \ 3 / '> mmaps.csv

(No estoy orgulloso de estas expresiones regulares, ¡pero hicieron el trabajo!)

A partir de las direcciones, determiné rápidamente que la mayoría de las nuevas fallas estaban ocurriendo en la región de la memoria que qfsd usa para la caché de datos de bloques. Mapeamos la memoria en la caché en fragmentos de 2 MiB, pero la usamos en bloques de 4 KiB; a continuación se muestra una visualización de las llamadas mmap y las fallas de página alrededor de una de esas regiones de 2 MiB cuando se usa Linux 4.13:

Observe el solitario punto naranja en la parte inferior izquierda, seguido de una cadena larga y larga de fallas de página (puntos azules) en la región de 2 MiB por encima de esa dirección base. (Tenga en cuenta también el orden en el que tiende a accederse a las páginas: ¡al revés en el espacio de direcciones! Este detalle ligeramente sorprendente se vuelve importante más adelante ...)

Corrí las mismas medidas contra nuestro kernel 4.8 build, y los resultados no pudieron ser más diferentes. ¡Aquí, solo hubo una o dos fallas de página subsiguientes a cada mmap!

A estas alturas, aquellos de ustedes que están mucho más familiarizados con la configuración de memoria virtual de Linux que yo, probablemente estén gritando la respuesta en sus mentes, o tal vez en voz alta. Pero yo, tuve que ir y aprender por el camino difícil. Al menos tuve la sensatez de sospechar que la respuesta podría encontrarse en la configuración de compilación del kernel, así que agarré los kconfigs para ambos kernels y comencé a dividir. (Usamos el kernel distribuido por Ubuntu, así que en su mayor parte tomamos las configuraciones predeterminadas. Muy conveniente, pero no tan bueno cuando algo se vuelve más lento y no sabes por qué)

Después de unas pocas iteraciones de bisección, la lista de diferencias de configuración restantes era lo suficientemente pequeña como para que la leyera cuidadosamente, y una configuración saltó hacia mí. Esta fue la diferencia:

- TRANSPARENT_HUGEPAGE_ALWAYS = y
+ TRANSPARENT_HUGEPAGE_MADVISE = y

Las páginas de apoyo transparentes son una característica del sistema de memoria de Linux que, cuando se habilita para una región de memoria, hace que el kernel use "enormes" páginas 2 de MiB para respaldar las asignaciones de memoria en lugar del habitual 4 KiB. Este cambio significó que THP pasó de estar activado de forma predeterminada a estar desactivado de forma predeterminada, y se realizó a la configuración predeterminada para las distribuciones Xenial y Artful de Ubuntu. Ver este problema de Launchpad para obtener más información acerca de por qué se realizó el cambio.

Casualmente, mmap 2 MiB a la vez en nuestro caché de datos, razón por la cual antes solíamos fallar una página por cada fragmento. ¡Misterio resuelto! Nuestros patrones de acceso realmente se benefician de las tablas de páginas más pequeñas y de los pocos fallos habilitados por THP, por lo que simplemente lo habilitamos nuevamente para nuestro proceso, y la mayor parte de la regresión de rendimiento desapareció.

Fallas de página II: Boogaloo eléctrico

Avance rápido al 2018 tardío. Mi equipo estaba trabajando en la optimización del rendimiento de cargas de trabajo de lectura aleatoria muy pesadas (es posible que haya visto publicaciones recientes en el blog de dos de mis colegas, Matt McMullan y Graham Ellis, sobre el mismo proyecto). Habíamos progresado mucho mejorando nuestro sistema de almacenamiento en caché de bloqueo distribuido, ajustando el programador de tareas, haciendo que la captación previa fuera más inteligente y reduciendo la contención de spinlock en varios lugares.

Sin embargo, notamos que había algunas fuentes extrañas de variabilidad en nuestro punto de referencia: algunas carreras fueron solo un poco más rápidas que otras, sin una razón obvia de inmediato. Una mirada a los datos de SAR mostró que las fallas de página estaban nuevamente fuertemente correlacionadas con la bimodalidad. Encontramos algunas cosas que podrían suavizar la forma en que se accedió a la memoria aquí, incluida la garantía de que inicialmente se asignó más caché de bloques, pero esta publicación de blog ya se está volviendo un poco larga, así que tal vez entraré en más detalles sobre aquellos en una fecha posterior!

Pero, ¿recuerda ese gráfico anterior, que muestra el patrón de acceso hacia atrás? Nosotros también lo recordamos y profundizamos un poco más. Resultó ser solo un efecto secundario de la forma en que nuestro caché vende ranuras, por lo que intentamos revertirlo, por lo que las tareas que solicitan ranuras de caché tenderían a colocarlas en orden ascendente de direcciones. Para nuestra sorpresa, ¡este rendimiento ligeramente mejorado! (Todavía no estamos del todo seguros de por qué, nuestro presentimiento es que Linux puede premapear o hacer alguna otra optimización en presencia de acceso secuencial).

Este cuadro muestra las mejoras relativas que hicimos en el punto de referencia de lectura aleatoria en el transcurso de diez semanas aproximadamente:

¡El último golpe fue para evitar fallos de página! Solo se muestra, a menudo es importante saber qué está haciendo su sistema operativo bajo el capó.

Comparta este artículo