1 puntos por GN⁺ 2025-05-11 | Aún no hay comentarios. | Compartir por WhatsApp
  • 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 char en 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 Rows sobre 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-k8 de 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 Parse
    • ParseColInfos: se usa cuando hay manejo de comillas y requiere más seguimiento de estado
    • ParseColEnds: se usa cuando no hay manejo de comillas
  • El parsing se realiza por bloques span de char obtenidos 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
  • 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, SepParserAvx512PackCmpOrMoveMaskTzcnt cargaba 32 char en 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.Equals terminaba en dos instrucciones, vpcmpeqb y vpmovm2b, repitiendo movimientos entre registros de máscara como k1 y registros vectoriales generales zmm
  • En Sep 0.10.0, la llamada a MoveMask se adelantó para reducir la cantidad de idas y vueltas entre registros de máscara y registros generales
    • En otros parsers, MoveMask solo se llama cuando hace falta, para reducir el número de instrucciones en la ruta rápida “sin caracteres especiales”
  • 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 SepParserAvx2PackCmpOrMoveMaskTzcnt basado 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 SepParserAvx512To256CmpOrMoveMaskTzcnt de la 0.10.0 carga char con instrucciones AVX-512 y luego crea un vector de bytes de 256 bits con ConvertToVector256ByteWithSaturation
    • La instrucción real es vpmovuswb
    • El procesamiento por loop es de “solo” 32 char, pero la estructura es más simple
  • 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.351ms
    • SepParserVector256NrwCmpExtMsbTzcnt: 20608.5 MB/s, 28.3 ns/row, 1.416ms
    • SepParserAvx2PackCmpOrMoveMaskTzcnt: 20599.3 MB/s, 28.3 ns/row, 1.417ms
    • SepParserAvx512PackCmpOrMoveMaskTzcnt: 19944.3 MB/s, 29.3 ns/row, 1.463ms
  • El parser multiplataforma basado en Vector256 llegó a un nivel casi igual al de AVX2
  • Los parsers multiplataforma basados en Vector128 y Vector512 siguieron siendo rápidos, pero fueron entre 5% y 10% más lentos, y Vector512 resultó más lento que Vector128
  • SepParserIndexOfAny quedó muy por detrás con 2787.0 MB/s, y Vector64 se 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
  • En el 9950X, Sep de 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/s
    • ReadLine_: 377.033ms, 1548.4 MB/s, asignación de 1991.04MB
    • CsvHelper: 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.

Aún no hay comentarios.