La vectorisation

La plupart des processeurs de nos jours disposent d'instructions Single Instruction Multiple Data (SIMD), qui permettent d'appliquer les mêmes opérations sur plusieurs valeurs à la fois (sur des float, des int, ...).

Les registres et instructions de la famille SSE sont les plus communs, et permettent de traiter par exemple 4 floats à la fois : les registres font 128 bits. De plus, les manipulations de nombres flottants (float, double) sont plus optimisées avec ces instructions que celles classiques, même sans appliquer de vectorisation.

La nouvelle famille de registres et d'instructions AVX est également disponible selon le processeur. Leurs registres de 256 et 512 bits permettent de manipuler 8 ou 16 floats à la fois !

Optimisation du code vectorisé

Voici un exemple de code vectorisé, avec l'assembleur correspondant (pour GCC). Il est commenté et est fait pour être modifié, afin de voir les changements qui s'effectuent du côté de l'assembleur.

Par défaut, l'exécutable généré doit être portable : le compilateur ne va donc pas utiliser d'instructions AVX. Les options qui vont restreindre les architectures ciblées vont donc permettre leur utilisation, typiquement avec -march=xxx.

Le possible aliasing des pointeurs utilisés peut être très nocif pour la vectorisation du code : dans le meilleur des cas, le compilateur va générer une boucle vectorisée et une autre non, puis va générer le test qui permet de sélectionner la boucle. Sinon, le code n'est tout simplement pas vectorisé.

D'autres optimisations existent, comme l'alignement en mémoire des données manipulées qui permet de les charger plus vite, et qui dépend de la taille des registres SIMD souhaités.

Les plus gros gain de performances ne viendront pas de l'optimisation de la vectorisation elle-même, mais plus de l'organisation du code qui permettra au compilateur de le vectoriser automatiquement. Par exemple, avec le pattern Arrays of Structures vs Structures of Arrays qui demande de modifier le design d'un programme.

Pour aller plus loin

Intel dispose d'un compilateur (Intel C++ Compiler, ou ICC) très efficace pour optimiser le code, notamment concernant la vectorisation.

Au lieu d'essayer de suggérer au compilateur quelles instructions utiliser, il vaut mieux parfois les utiliser directement : certains compilateurs mettent à dispositions ces instructions (comme ICC), mais il peut sinon valoir le coup d'écrire directement le code en assembleur avec __asm.

Utiliser des instructions AVX puis SSE (ou l'inverse) nécessite au compilateur de changer de mode, ce qui peut coûter de nombreux cycles selon le processeur. Des solutions spécifiques aux processeurs existent.

Last updated