Contexte et motivations
Go 1.26 a ajouté une API SIMD limitée à l’architecture amd64. L’édition 1.27 a étendu ce support aux processeurs arm64 (NEON) et aux modules WebAssembly disposant d’instructions SIMD. Avant ces ajouts, l’accès aux instructions SIMD depuis Go nécessitait d’écrire du code en assembleur, ce qui restreignait l’optimisation aux seuls noyaux de calcul ultra‑critiques. La plupart des applications de traitement de données, de cryptographie ou d’IA laissaient ainsi le CPU sous‑exploité.
Le nouveau paquet simd, expérimental depuis Go 1.27, vise à fournir une interface « write‑once », proche des performances de l’assembleur, tout en offrant une émulation fiable sur les plateformes dépourvues de SIMD. Cette ambition répond à la diversité des jeux d’instructions et à la difficulté de gérer les variations de taille et de masquage des vecteurs.
Architecture du package simd
Le paquet repose sur une couche d’abstraction qui supprime les vecteurs de taille fixe du système de types Go. Au lieu de Vector128 ou Vector256, il expose des types génériques dont la taille est déterminée à l’exécution. Les opérations disponibles correspondent à l’intersection des capacités communes à AVX, AVX2, AVX512, NEON et aux instructions SIMD de wasm. Lorsqu’une instruction n’est pas nativement supportée, le runtime compose l’opération à partir d’instructions SIMD plus simples, assurant ainsi une émulation efficace.
Pour activer le paquet, il suffit de définir la variable d’environnement GOEXPERIMENT=simd, de la même façon que pour le paquet architecture‑spécifique archsimd. Ainsi, le même code source peut être compilé pour plusieurs cibles sans modification, le compilateur sélectionnant la meilleure implémentation disponible.
Gestion des variations d’architectures SIMD
Les architectures diffèrent sur trois axes majeurs : taille du vecteur, masquage et jeu d’instructions. Par exemple, wasm ne propose que des vecteurs de 128 bits, alors que amd64 supporte 128, 256 et 512 bits (AVX, AVX2, AVX512). RISC‑V autorise des vecteurs de 128 à 65 536 bits, uniquement en puissances de deux. Arm64 propose NEON (128 bits) et SVE (128‑2048 bits, pas toujours disponible). Le paquet simd interroge les capacités du processeur au démarrage : il détecte la présence d’AVX2, d’AVX512 ou de SVE2, puis ajuste dynamiquement la largeur de vecteur utilisée.
Le masquage constitue un autre point de divergence. AVX512 possède des registres de masque dédiés, alors que NEON et wasm utilisent des vecteurs booléens classiques. Le paquet implémente une abstraction de masque qui, selon la plateforme, se traduit soit par un registre matériel, soit par une opération de comparaison suivie d’une sélection conditionnelle. Cette approche minimise le coût d’une opération masquée tout en conservant la portabilité du code.
Implications, performances et limites
Lorsque le code source utilise uniquement les opérations de l’intersection, le compilateur génère des instructions SIMD natives, offrant des performances proches de l’assembleur écrit à la main. Sur des processeurs supportant AVX512, par exemple, une addition de huit float64 se traduit par une instruction vaddpd à 512 bits. Sur des cibles sans SIMD, le même appel est émulé par une boucle scalar, ce qui garantit la fonctionnalité mais entraîne une perte de débit proportionnelle à la largeur du vecteur simulé.
Le principal compromis réside dans la restriction du jeu d’instructions : les algorithmes qui tirent parti de primitives spécifiques (ex. : permutations variables, comparaisons 64‑bits sur wasm) ne peuvent pas être exprimés directement via simd. De plus, la couche d’émulation ajoute une surcharge de compilation et de runtime, notamment lors de la détection des capacités matérielles. Enfin, le paquet reste expérimental ; il n’est pas encore intégré dans la chaîne de production standard de Go, et son API peut évoluer.