1 pontos por GN⁺ 2025-05-11 | Ainda não há comentários. | Compartilhar no WhatsApp
  • 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 char em 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 Rows de 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-k8 do 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 Parse
    • ParseColInfos: usado quando há tratamento de aspas e exige rastreamento de mais estado
    • ParseColEnds: usado quando não há tratamento de aspas
  • O parsing é feito por unidades de span de char trazidas 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
  • 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 SepParserAvx512PackCmpOrMoveMaskTzcnt do 0.9.0 carregava 32 char em 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.Equals era seguido por duas instruções, vpcmpeqb e vpmovm2b, repetindo transferências entre registradores de máscara como k1 e registradores vetoriais comuns zmm
  • No Sep 0.10.0, a chamada de MoveMask foi 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”
  • 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 SepParserAvx2PackCmpOrMoveMaskTzcnt baseado 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 SepParserAvx512To256CmpOrMoveMaskTzcnt do 0.10.0 carrega char com instruções AVX-512 e depois cria um vetor de bytes de 256 bits com ConvertToVector256ByteWithSaturation
    • A instrução real é vpmovuswb
    • A quantidade processada por loop é “apenas” 32 char, mas a estrutura é mais simples
  • 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.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
  • O parser multiplataforma baseado em Vector256 chegou a um nível muito próximo do AVX2
  • Os parsers multiplataforma baseados em Vector128 e Vector512 ainda foram rápidos, mas ficaram de 5% a 10% atrás, e o Vector512 foi mais lento que o Vector128
  • O SepParserIndexOfAny ficou bem para trás com 2787.0 MB/s, e o Vector64 nã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
  • No 9950X, o Sep de 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/s
    • ReadLine_: 377.033ms, 1548.4 MB/s, alocação de 1991.04MB
    • CsvHelper: 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.

Ainda não há comentários.