AMD 9950X atinge 21 GB/s em parsing de CSV com SIMD
(nietras.com)- O Sep 0.10.0 ficou mais rápido que antes no parsing de CSV de baixo nível ao atingir 21 GB/s com otimizações para CPUs com suporte a AVX-512 como a AMD 9950X (Zen 5), contra cerca de 18 GB/s anteriormente
- O ganho de desempenho veio de mudanças na estrutura do parser para reduzir o gargalo de ida e volta de registradores de máscara na geração de código AVX-512 do .NET 9.0
- O novo parser AVX-512-to-256 carrega
charem 512 bits e depois converte para vetores de bytes de 256 bits, evitando o custo do processamento de máscara e de uma permutação separada - O desempenho de parsing de baixo nível do Sep melhorou cerca de 3x, saindo de aproximadamente 7 GB/s no 0.1.0 em 2023 com 5950X/.NET 7.0 para cerca de 21 GB/s no 0.10.0 com 9950X/.NET 9.0
- Em benchmarks de nível mais alto, o Sep multithread no 9950X processou 1 milhão de linhas de package assets em 72.213ms, cerca de 8.0 GB/s, e também registrou aproximadamente 8.1 GB/s com dados de floats
Objetivo e resultado do Sep 0.10.0
- O Sep 0.10.0 foi lançado em 22 de abril de 2025 e inclui otimizações para CPUs com suporte a AVX-512 como a AMD 9950X (Zen 5), além de benchmarks no 9950X
- No critério de parsing de CSV de baixo nível, o Sep atingiu 21 GB/s no 9950X
- Antes do 0.10.0, era cerca de 18 GB/s no mesmo 9950X
- O escopo da análise cobre o parsing
Rowsde baixo nível de dados CSV de package assets, e todos os números são de thread única - Os números de benchmark podem variar alguns pontos percentuais, então pequenas regressões podem aparecer em releases específicas
Mudança de desempenho do 0.1.0 ao 0.10.0
- O desempenho do Sep foi melhorando de forma gradual com a combinação de mudanças no código, evolução das versões do .NET e mudanças de geração de CPU
- O fluxo representativo de desempenho é o seguinte
- 0.1.0, 5950X, .NET 7.0: cerca de 7 GB/s
- 0.3.0, 5950X, .NET 8.0: cerca de 12 GB/s
- 0.6.0, 5950X, .NET 9.0: cerca de 13 GB/s
- 0.9.0, 9950X, .NET 9.0: cerca de 18 GB/s
- 0.10.0, 9950X, .NET 9.0: cerca de 21 GB/s
- Desde a apresentação pública do Sep em junho de 2023, ele ficou cerca de 3x mais rápido em um período de pouco menos de 2 anos
- Comparando o uso do Sep 0.9.0 no 5950X com o uso do Sep 0.10.0 no 9950X, a melhora é de cerca de 1.6x
- O boost clock do 9950X é 5.7GHz, enquanto o do 5950X é 4.9GHz
- Considera-se que só essa diferença de clock pode explicar cerca de 1.2x
Gargalo de registradores de máscara na geração de código AVX-512 do .NET
- O Sep oferece suporte a AVX-512 desde o 0.2.3, mas na época o .NET 8 não dava suporte explícito aos registradores de máscara
k1-k8do AVX-512 - Na geração de código AVX-512 existente, o resultado das comparações entrava no registrador de máscara, depois era movido para um registrador comum e em seguida voltava para o registrador de máscara
- Após o upgrade para o 9950X, o Sep 0.9.0 registrou cerca de 18 GB/s no parsing de CSV de baixo nível, o que era aproximadamente 1.4x mais rápido que no 5950X
- Ao trocar o parser por variável de ambiente para comparação, o parser AVX2 marcou cerca de 20 GB/s no 9950X, aproximadamente 10% mais rápido que o parser AVX-512 anterior
- Essa diferença confirmou que o tratamento de registradores de máscara do AVX-512 ainda impactava o desempenho
Estrutura básica do loop de parsing do Sep
- Todos os parsers do Sep seguem a mesma estrutura básica e dão suporte a dois caminhos com tratamento diferente de aspas por meio de um único método genérico
ParseParseColInfos: usado quando há tratamento de aspas e exige rastreamento de mais estadoParseColEnds: usado quando não há tratamento de aspas
- O parsing é feito por unidades de span de
chartrazidas de um array, e o exemplo usa tamanho 16K- Esse tamanho é pequeno o bastante para caber no cache da CPU e também é vantajoso para multithreading eficiente depois
- O loop carrega dados de caracteres de 16 bits em registradores SIMD, converte isso para registradores SIMD de bytes e então compara com caracteres especiais de CSV
- Entre os alvos de comparação estão
\n,\r,",;e outros
- Entre os alvos de comparação estão
- Os resultados das comparações são convertidos em bitmasks, e apenas os bits marcados na máscara são parseados em sequência
- As diferenças de desempenho dependem fortemente de como esse código SIMD em C# é compilado por JIT em código de máquina no .NET
O parser AVX-512 anterior e os ajustes do 0.10.0
- O
SepParserAvx512PackCmpOrMoveMaskTzcntdo 0.9.0 carregava 32charem cada um de dois registradores SIMD de 512 bits, empacotando isso em um único vetor de bytes de 512 bits para processar 64 caracteres por loop - Os dados empacotados ficavam com a ordem embaralhada, então era preciso reorganizá-los com
PermuteVar8x64 - No assembly do .NET 9.0, cada
Vec.Equalsera seguido por duas instruções,vpcmpeqbevpmovm2b, repetindo transferências entre registradores de máscara comok1e registradores vetoriais comunszmm - No Sep 0.10.0, a chamada de
MoveMaskfoi antecipada para reduzir a quantidade de idas e voltas entre registradores de máscara e registradores comuns- Em outros parsers,
MoveMaské chamado apenas quando necessário para reduzir o número de instruções no caminho rápido de “sem caracteres especiais”
- Em outros parsers,
- Mesmo após o ajuste, a movimentação do registrador de máscara para o registrador comum continua existindo, mas o número total de instruções de assembly diminui
AVX2 e o novo parser AVX-512-to-256
- O assembly do
SepParserAvx2PackCmpOrMoveMaskTzcntbaseado em AVX2 tem uma estrutura mais direta por não usar registradores de máscara - Graças a essa estrutura, o parser AVX2 era mais rápido que o parser AVX-512 anterior do 0.9.0
- O novo
SepParserAvx512To256CmpOrMoveMaskTzcntdo 0.10.0 carregacharcom instruções AVX-512 e depois cria um vetor de bytes de 256 bits comConvertToVector256ByteWithSaturation- A instrução real é
vpmovuswb - A quantidade processada por loop é “apenas” 32
char, mas a estrutura é mais simples
- A instrução real é
- Essa abordagem evita o problema dos registradores de máscara de 512 bits, e os dados empacotados já ficam na ordem correta em
ymm4, então também não é necessária uma permutação separada - O novo parser elevou o desempenho de parsing do Sep no 9950X para cerca de 21 GB/s
Benchmarks de baixo nível por parser no 9950X
- Ao executar todos os parsers por variável de ambiente na AMD 9950X para comparação, o novo parser AVX-512-to-256 foi o mais rápido
- Os principais resultados foram os seguintes
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
- O parser multiplataforma baseado em
Vector256chegou a um nível muito próximo do AVX2 - Os parsers multiplataforma baseados em
Vector128eVector512ainda foram rápidos, mas ficaram de 5% a 10% atrás, e oVector512foi mais lento que oVector128 - O
SepParserIndexOfAnyficou bem para trás com 2787.0 MB/s, e oVector64não foi acelerado no 9950X, permanecendo em 459.9 MB/s
Benchmarks de nível mais alto no 5950X e no 9950X
- No processamento de 1 milhão de linhas de dados de package assets, o 9950X foi muito mais rápido que o 5950X
- 5950X
Sep_MT: 119.430ms, 4888.1 MB/s - 9950X
Sep_MT: 72.213ms, 8084.1 MB/s
- 5950X
- No 9950X, o
Sepde thread única processou 1 milhão de linhas de package assets em 291.979ms, 1999.4 MB/s - No mesmo benchmark de package assets no 9950X, os comparativos mostraram o seguinte desempenho
Sylvan: 413.265ms, 1412.6 MB/sReadLine_: 377.033ms, 1548.4 MB/s, alocação de 1991.04MBCsvHelper: 1005.323ms, 580.7 MB/s
- Também com dados de floats, o Sep multithread no 9950X processou 25.000 linhas em 2.497ms, 8136.8 MB/s
- A melhora dos benchmarks de nível mais alto do 5950X para o 9950X também ficou em torno de 1.5x a 1.6x, de forma semelhante aos benchmarks de baixo nível
Ainda não há comentários.