Logran 21 GB/s de parsing CSV con SIMD en un AMD 9950X
(nietras.com)- Sep 0.10.0 logró 21 GB/s en parsing CSV de bajo nivel gracias a optimizaciones para CPU con soporte AVX-512 como el AMD 9950X (Zen 5), superando los ~18 GB/s anteriores
- La mejora de rendimiento proviene de cambiar la estructura del parser para reducir el cuello de botella causado por los viajes de ida y vuelta de registros de máscara en la generación de código AVX-512 de .NET 9.0
- El nuevo parser AVX-512-to-256 carga
charen 512 bits y luego los convierte a vectores de bytes de 256 bits, evitando el costo del manejo de máscaras y de una permutación separada - El rendimiento de parsing de bajo nivel de Sep mejoró aproximadamente 3 veces, desde unos 7 GB/s en la versión 0.1.0 de 2023 con 5950X/.NET 7.0 hasta unos 21 GB/s en 0.10.0 con 9950X/.NET 9.0
- En benchmarks de nivel superior, Sep multihilo en 9950X procesó 1 millón de filas de package assets en 72.213ms, alrededor de 8.0 GB/s, y también registró unos 8.1 GB/s con datos de floats
Objetivos y resultados de Sep 0.10.0
- Sep 0.10.0 se lanzó el 22 de abril de 2025 e incluye optimizaciones para CPU con soporte AVX-512 como el AMD 9950X (Zen 5), además de benchmarks en 9950X
- En parsing CSV de bajo nivel, Sep alcanzó 21 GB/s en el 9950X
- Antes de la 0.10.0, en el mismo 9950X rondaba los 18 GB/s
- El análisis cubre el parsing de bajo nivel de
Rowssobre datos CSV de package assets, y todas las cifras corresponden a un solo hilo - Las cifras de benchmark pueden variar algunos puntos porcentuales, por lo que en una versión específica podrían verse pequeñas regresiones
Cambios de rendimiento desde 0.1.0 hasta 0.10.0
- El rendimiento de Sep fue mejorando gradualmente por el efecto combinado de cambios de código, nuevas versiones de .NET y nuevas generaciones de CPU
- El recorrido representativo del rendimiento es el siguiente
- 0.1.0, 5950X, .NET 7.0: alrededor de 7 GB/s
- 0.3.0, 5950X, .NET 8.0: alrededor de 12 GB/s
- 0.6.0, 5950X, .NET 9.0: alrededor de 13 GB/s
- 0.9.0, 9950X, .NET 9.0: alrededor de 18 GB/s
- 0.10.0, 9950X, .NET 9.0: alrededor de 21 GB/s
- Desde la publicación de Sep en junio de 2023, se volvió casi 3 veces más rápido en un periodo de menos de 2 años
- Si se compara Sep 0.9.0 en un 5950X con Sep 0.10.0 en un 9950X, la mejora es de alrededor de 1.6 veces
- El boost clock del 9950X es de 5.7GHz y el del 5950X es de 4.9GHz
- Se estima que solo esta diferencia de frecuencia explica cerca de 1.2 veces
Cuello de botella de registros de máscara en la generación de código AVX-512 de .NET
- Sep soporta AVX-512 desde la versión 0.2.3, pero en ese momento .NET 8 no tenía soporte explícito para los registros de máscara
k1-k8de AVX-512 - En la generación de código AVX-512 anterior, el resultado de una comparación entraba a un registro de máscara, luego se movía a un registro general y después regresaba al registro de máscara
- Tras actualizar al 9950X, Sep 0.9.0 registró unos 18 GB/s en parsing CSV de bajo nivel, alrededor de 1.4 veces más rápido que en el 5950X
- Al comparar cambiando el parser mediante variables de entorno, el parser AVX2 alcanzó unos 20 GB/s en el 9950X, aproximadamente un 10% más rápido que el parser AVX-512 anterior
- Esa diferencia confirmó que el manejo de registros de máscara de AVX-512 seguía afectando el rendimiento
Estructura base del loop de parsing de Sep
- Todos los parsers de Sep siguen la misma estructura básica y soportan dos rutas con distinto manejo de comillas mediante un solo método genérico
ParseParseColInfos: se usa cuando hay manejo de comillas y requiere más seguimiento de estadoParseColEnds: se usa cuando no hay manejo de comillas
- El parsing se realiza por bloques
spandecharobtenidos de un arreglo; en el ejemplo, el tamaño es de 16K- Ese tamaño es lo bastante pequeño para caber en caché de CPU y también favorece el multihilo eficiente después
- El loop carga datos de caracteres de 16 bits en registros SIMD, los convierte a registros SIMD de bytes y luego compara caracteres especiales de CSV
- Entre los caracteres comparados están
\n,\r,",;y otros
- Entre los caracteres comparados están
- Los resultados de comparación se convierten en bitmasks, y solo los bits activados en la máscara se procesan secuencialmente
- La diferencia de rendimiento depende en gran medida de qué código máquina genera el JIT de .NET a partir de este código SIMD en C#
El parser AVX-512 anterior y los ajustes de la 0.10.0
- En 0.9.0,
SepParserAvx512PackCmpOrMoveMaskTzcntcargaba 32charen cada uno de dos registros SIMD de 512 bits, los empaquetaba en un solo vector de bytes de 512 bits y procesaba 64 caracteres por iteración del loop - Los datos empaquetados quedaban en orden mezclado, por lo que había que reordenarlos con
PermuteVar8x64 - En el ensamblador de .NET 9.0, cada
Vec.Equalsterminaba en dos instrucciones,vpcmpeqbyvpmovm2b, repitiendo movimientos entre registros de máscara comok1y registros vectoriales generaleszmm - En Sep 0.10.0, la llamada a
MoveMaskse adelantó para reducir la cantidad de idas y vueltas entre registros de máscara y registros generales- En otros parsers,
MoveMasksolo se llama cuando hace falta, para reducir el número de instrucciones en la ruta rápida “sin caracteres especiales”
- En otros parsers,
- Incluso después del ajuste, sigue existiendo el movimiento de registros de máscara a registros generales, pero el número total de instrucciones de ensamblador disminuye
AVX2 y el nuevo parser AVX-512-to-256
- El ensamblador del
SepParserAvx2PackCmpOrMoveMaskTzcntbasado en AVX2 tiene una estructura más directa porque no usa registros de máscara - Gracias a eso, el parser AVX2 era más rápido que el parser AVX-512 anterior de la 0.9.0
- El nuevo
SepParserAvx512To256CmpOrMoveMaskTzcntde la 0.10.0 cargacharcon instrucciones AVX-512 y luego crea un vector de bytes de 256 bits conConvertToVector256ByteWithSaturation- La instrucción real es
vpmovuswb - El procesamiento por loop es de “solo” 32
char, pero la estructura es más simple
- La instrucción real es
- Este enfoque evita el problema de los registros de máscara de 512 bits y tampoco necesita una permutación aparte, porque los datos empaquetados ya quedan en el orden correcto dentro de
ymm4 - El nuevo parser elevó el rendimiento de parsing de Sep en el 9950X hasta unos 21 GB/s
Benchmarks de bajo nivel por parser en el 9950X
- Al ejecutar todos los parsers en un AMD 9950X mediante variables de entorno, el nuevo parser AVX-512-to-256 fue el más rápido
- Los principales resultados fueron los siguientes
SepParserAvx512To256CmpOrMoveMaskTzcnt: 21597.7 MB/s, 27.0 ns/row, 1.351msSepParserVector256NrwCmpExtMsbTzcnt: 20608.5 MB/s, 28.3 ns/row, 1.416msSepParserAvx2PackCmpOrMoveMaskTzcnt: 20599.3 MB/s, 28.3 ns/row, 1.417msSepParserAvx512PackCmpOrMoveMaskTzcnt: 19944.3 MB/s, 29.3 ns/row, 1.463ms
- El parser multiplataforma basado en
Vector256llegó a un nivel casi igual al de AVX2 - Los parsers multiplataforma basados en
Vector128yVector512siguieron siendo rápidos, pero fueron entre 5% y 10% más lentos, yVector512resultó más lento queVector128 SepParserIndexOfAnyquedó muy por detrás con 2787.0 MB/s, yVector64se quedó en 459.9 MB/s porque no recibe aceleración en el 9950X
Benchmarks de nivel superior en 5950X y 9950X
- En los datos de package assets, el resultado procesando 1 millón de filas mostró que el 9950X fue claramente más rápido que el 5950X
- 5950X
Sep_MT: 119.430ms, 4888.1 MB/s - 9950X
Sep_MT: 72.213ms, 8084.1 MB/s
- 5950X
- En el 9950X,
Sepde un solo hilo procesó 1 millón de filas de package assets en 291.979ms, a 1999.4 MB/s - En el mismo benchmark de package assets sobre el 9950X, los comparados mostraron el siguiente rendimiento
Sylvan: 413.265ms, 1412.6 MB/sReadLine_: 377.033ms, 1548.4 MB/s, asignación de 1991.04MBCsvHelper: 1005.323ms, 580.7 MB/s
- También en datos de floats, Sep multihilo en el 9950X procesó 25,000 filas en 2.497ms, a 8136.8 MB/s
- La mejora en benchmarks de nivel superior al pasar del 5950X al 9950X fue similar a la de los benchmarks de bajo nivel: alrededor de 1.5 a 1.6 veces
Aún no hay comentarios.