1 points par GN⁺ 2025-05-11 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • Sep 0.10.0 atteint 21 Go/s en parsing CSV bas niveau grâce à des optimisations pour les CPU compatibles AVX-512 comme l’AMD 9950X (Zen 5), contre environ 18 Go/s auparavant
  • Le gain de performance vient d’une restructuration du parser pour réduire le goulot d’étranglement lié aux allers-retours des registres de masque dans la génération de code AVX-512 de .NET 9.0
  • Le nouveau parser AVX-512-to-256 charge les char en 512 bits puis les convertit en vecteurs d’octets 256 bits, évitant le traitement des masques et le coût d’une permutation séparée
  • Les performances de parsing bas niveau de Sep se sont améliorées d’environ , passant d’environ 7 Go/s avec la version 0.1.0 en 2023 sur 5950X/.NET 7.0 à environ 21 Go/s avec la version 0.10.0 sur 9950X/.NET 9.0
  • Dans les benchmarks de plus haut niveau, Sep multithread sur 9950X traite aussi 1 million de lignes de package assets en 72,213 ms, soit environ 8,0 Go/s, et les données floats atteignent également environ 8,1 Go/s

Objectifs et résultats de Sep 0.10.0

  • Sep 0.10.0 est sorti le 22 avril 2025 et inclut des optimisations pour les CPU compatibles AVX-512 comme l’AMD 9950X (Zen 5), ainsi que des benchmarks sur 9950X
  • En parsing CSV bas niveau, Sep atteint 21 Go/s sur 9950X
    • Avant la version 0.10.0, il était à environ 18 Go/s sur le même 9950X
  • L’analyse porte sur le parsing bas niveau Rows de données CSV package assets, et tous les chiffres sont en monothread
  • Les chiffres de benchmark peuvent varier de quelques points de pourcentage, de petites régressions pouvant donc apparaître sur certaines releases

Évolution des performances de 0.1.0 à 0.10.0

  • Les performances de Sep se sont progressivement améliorées sous l’effet combiné des changements de code, des versions de .NET et des générations de CPU
  • La progression représentative est la suivante
    • 0.1.0, 5950X, .NET 7.0 : environ 7 Go/s
    • 0.3.0, 5950X, .NET 8.0 : environ 12 Go/s
    • 0.6.0, 5950X, .NET 9.0 : environ 13 Go/s
    • 0.9.0, 9950X, .NET 9.0 : environ 18 Go/s
    • 0.10.0, 9950X, .NET 9.0 : environ 21 Go/s
  • Depuis la publication de Sep en juin 2023, il est devenu environ plus rapide en un peu moins de deux ans
  • En comparant Sep 0.9.0 sur 5950X à Sep 0.10.0 sur 9950X, l’amélioration est d’environ 1,6×
    • La fréquence boost du 9950X est de 5,7 GHz, contre 4,9 GHz pour le 5950X
    • Cet écart de fréquence est considéré comme pouvant expliquer à lui seul un facteur d’environ 1,2×

Goulot d’étranglement des registres de masque dans la génération de code AVX-512 de .NET

  • Sep prend en charge AVX-512 depuis la version 0.2.3, mais .NET 8 ne prenait alors pas explicitement en charge les registres de masque k1-k8 d’AVX-512
  • Dans l’ancienne génération de code AVX-512, le résultat d’une comparaison était placé dans un registre de masque, puis transféré vers un registre général, avant de revenir dans un registre de masque
  • Après la mise à niveau vers le 9950X, Sep 0.9.0 a atteint environ 18 Go/s en parsing CSV bas niveau, soit environ 1,4× plus rapide que sur 5950X
  • En changeant de parser via une variable d’environnement pour comparer, le parser AVX2 a atteint environ 20 Go/s sur 9950X, soit environ 10 % de plus que l’ancien parser AVX-512
  • Cet écart a confirmé que le traitement des registres de masque AVX-512 continuait d’affecter les performances

Structure de base de la boucle de parsing de Sep

  • Tous les parsers de Sep suivent la même structure de base, avec une méthode générique Parse unique qui prend en charge deux chemins selon que les guillemets doivent être traités ou non
    • ParseColInfos : utilisé lorsque les guillemets sont traités, avec davantage de suivi d’état nécessaire
    • ParseColEnds : utilisé lorsqu’il n’y a pas de traitement des guillemets
  • Le parsing s’effectue par spans de char issus d’un tableau, avec une taille de 16K dans l’exemple
    • Cette taille est assez petite pour tenir dans le cache CPU, et elle est aussi favorable à un multithreading efficace ensuite
  • La boucle charge des données de caractères 16 bits dans des registres SIMD, les convertit en registres SIMD d’octets, puis compare les caractères spéciaux CSV
    • Les cibles de comparaison incluent \n, \r, ", ;, etc.
  • Le résultat de la comparaison est converti en bitmask, puis seuls les bits définis dans le masque sont parsés séquentiellement
  • Les écarts de performance dépendent fortement de la façon dont ce code C# SIMD est compilé JIT en code machine par .NET

Ancien parser AVX-512 et ajustements de la version 0.10.0

  • Le SepParserAvx512PackCmpOrMoveMaskTzcnt de la version 0.9.0 charge 32 char dans chacun de deux registres SIMD 512 bits, puis les compacte en un vecteur d’octets 512 bits afin de traiter 64 caractères par boucle
  • Les données compactées étant dans un ordre mélangé, elles doivent être réordonnées avec PermuteVar8x64
  • Dans l’assembleur produit par .NET 9.0, chaque Vec.Equals se traduisait par deux instructions, vpcmpeqb et vpmovm2b, avec des transferts répétés entre les registres de masque comme k1 et les registres vectoriels généraux zmm
  • Dans Sep 0.10.0, l’appel à MoveMask est avancé afin de réduire le nombre d’allers-retours entre registres de masque et registres généraux
    • Dans les autres parsers, MoveMask n’est appelé qu’en cas de besoin afin de réduire le nombre d’instructions sur le chemin rapide « sans caractères spéciaux »
  • Même après cet ajustement, le transfert depuis le registre de masque vers le registre général demeure, mais le nombre total d’instructions assembleur diminue

AVX2 et nouveau parser AVX-512-to-256

  • L’assembleur du SepParserAvx2PackCmpOrMoveMaskTzcnt basé sur AVX2 a une structure plus directe, car il n’utilise pas de registres de masque
  • Grâce à cette structure, le parser AVX2 était plus rapide que l’ancien parser AVX-512 de la version 0.9.0
  • Le nouveau SepParserAvx512To256CmpOrMoveMaskTzcnt de la version 0.10.0 charge les char avec des instructions AVX-512, puis crée un vecteur d’octets 256 bits avec ConvertToVector256ByteWithSaturation
    • L’instruction réelle est vpmovuswb
    • Le débit par boucle n’est « que » de 32 char, mais la structure est plus simple
  • Cette approche évite le problème des registres de masque 512 bits, et les données compactées sont déjà dans le bon ordre dans ymm4, sans permutation séparée nécessaire
  • Le nouveau parser porte les performances de parsing de Sep à environ 21 Go/s sur 9950X

Benchmarks bas niveau par parser sur 9950X

  • En exécutant tous les parsers via des variables d’environnement sur AMD 9950X, le nouveau parser AVX-512-to-256 s’est révélé le plus rapide
  • Les principaux résultats sont les suivants
    • SepParserAvx512To256CmpOrMoveMaskTzcnt : 21597,7 Mo/s, 27,0 ns/ligne, 1,351 ms
    • SepParserVector256NrwCmpExtMsbTzcnt : 20608,5 Mo/s, 28,3 ns/ligne, 1,416 ms
    • SepParserAvx2PackCmpOrMoveMaskTzcnt : 20599,3 Mo/s, 28,3 ns/ligne, 1,417 ms
    • SepParserAvx512PackCmpOrMoveMaskTzcnt : 19944,3 Mo/s, 29,3 ns/ligne, 1,463 ms
  • Le parser multiplateforme basé sur Vector256 atteint presque le même niveau qu’AVX2
  • Les parsers multiplateformes basés sur Vector128 et Vector512 restent rapides, mais sont 5 à 10 % plus lents, et Vector512 est plus lent que Vector128
  • SepParserIndexOfAny est nettement à la traîne avec 2787,0 Mo/s, tandis que Vector64 n’est pas accéléré sur 9950X et reste à 459,9 Mo/s

Benchmarks de plus haut niveau sur 5950X et 9950X

  • Sur les données package assets, le traitement de 1 million de lignes est nettement plus rapide sur 9950X que sur 5950X
    • 5950X Sep_MT : 119,430 ms, 4888,1 Mo/s
    • 9950X Sep_MT : 72,213 ms, 8084,1 Mo/s
  • Sur 9950X, Sep en monothread traite 1 million de lignes package assets en 291,979 ms, soit 1999,4 Mo/s
  • Dans le même benchmark package assets sur 9950X, les solutions comparées affichent les performances suivantes
    • Sylvan : 413,265 ms, 1412,6 Mo/s
    • ReadLine_ : 377,033 ms, 1548,4 Mo/s, allocations 1991,04 Mo
    • CsvHelper : 1005,323 ms, 580,7 Mo/s
  • Sur les données floats, Sep multithread sur 9950X traite également 25 000 lignes en 2,497 ms, soit 8136,8 Mo/s
  • L’amélioration des benchmarks de plus haut niveau entre 5950X et 9950X est d’environ 1,5 à 1,6×, proche de celle observée dans les benchmarks bas niveau

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.