web-dev-qa-db-fra.com

Pourquoi la vectorisation, plus rapide en général, que les boucles?

Pourquoi, au niveau le plus bas du matériel effectuant les opérations et les opérations sous-jacentes générales impliquées (c'est-à-dire: les choses générales à toutes les implémentations réelles de tous les langages de programmation lors de l'exécution de code), la vectorisation est-elle généralement tellement plus rapide que la boucle?

Que fait l'ordinateur en boucle qu'il ne fait pas en utilisant la vectorisation (je parle des calculs réels que l'ordinateur effectue, pas de ce que le programmeur écrit), ou que fait-il différemment?

Je n'ai pas pu me convaincre pourquoi la différence devrait être si importante. Je pourrais probablement être persuadé que le code vectorisé rase quelque part les boucles en boucle quelque part, mais l'ordinateur doit toujours effectuer le même nombre d'opérations, n'est-ce pas? Par exemple, si nous multiplions un vecteur de taille N par un scalaire, nous aurons N multiplications à effectuer dans les deux sens, n'est-ce pas?

38
Ben Sandeen

La vectorisation (comme le terme est normalement utilisé) fait référence au fonctionnement SIMD (instruction unique, données multiples).

Cela signifie, en substance, qu'une instruction effectue la même opération sur un certain nombre d'opérandes en parallèle. Par exemple, pour multiplier un vecteur de taille N par un scalaire, appelons M le nombre d'opérandes de cette taille sur lesquels il peut opérer simultanément. Si c'est le cas, alors le nombre d'instructions dont il a besoin pour exécuter est approximativement N/M, où (avec des opérations purement scalaires) il devrait effectuer N opérations.

Par exemple, le jeu d'instructions AVX 2 actuel d'Intel utilise des registres 256 bits. Ceux-ci peuvent être utilisés pour contenir (et opérer) un ensemble de 4 opérandes de 64 bits chacun, ou 8 opérandes de 32 bits chacun.

Donc, en supposant que vous avez affaire à des nombres réels 32 bits à simple précision, cela signifie qu'une seule instruction peut effectuer 8 opérations (multiplications, dans votre cas) à la fois, donc (au moins en théorie), vous pouvez terminer N multiplications en utilisant seulement N/8 instructions de multiplication. Au moins, en théorie, cela devrait permettre à l'opération de se terminer environ 8 fois plus vite que l'exécution d'une instruction à la fois ne le permettrait.

Bien sûr, l'avantage exact dépend du nombre d'opérandes que vous prenez en charge par instruction. Les premières tentatives d'Intel ne prenaient en charge que les registres 64 bits, donc pour fonctionner sur 8 éléments à la fois, ces éléments ne pouvaient être que de 8 bits chacun. Ils prennent actuellement en charge les registres 256 bits, et ils ont annoncé la prise en charge de 512 bits (et ils l'ont peut-être même fourni dans quelques processeurs haut de gamme, mais pas dans les processeurs grand public normaux, du moins pour le moment). Faire bon usage de cette capacité peut également être non trivial, pour le moins. Planifier des instructions pour que vous ayez réellement N opérandes disponibles et aux bons endroits au bon moment n'est pas nécessairement une tâche facile (du tout).

Pour mettre les choses en perspective, le (désormais ancien) Cray 1 a gagné beaucoup de sa vitesse exactement de cette façon. Son unité vectorielle fonctionnait sur des ensembles de 64 registres de 64 bits chacun, ce qui lui permettait d'effectuer 64 opérations en double précision par cycle d'horloge. Sur un code vectorisé de manière optimale, il était beaucoup plus proche de la vitesse d'un processeur actuel que ce à quoi vous pourriez vous attendre en fonction uniquement de sa vitesse d'horloge (beaucoup plus faible). Profiter pleinement de cela n'a pas toujours été facile (et ne l'est toujours pas).

Gardez à l'esprit, cependant, que la vectorisation est pas la seule façon dont un CPU peut effectuer des opérations en parallèle. Il existe également la possibilité d'un parallélisme au niveau de l'instruction, qui permet à un seul processeur (ou au cœur unique d'un processeur) d'exécuter plusieurs instructions à la fois. La plupart des processeurs modernes incluent du matériel pour (théoriquement) exécuter jusqu'à environ 4 instructions par cycle d'horloge si les instructions sont un mélange de charges, de magasins et d'ALU. Ils peuvent exécuter en moyenne près de 2 instructions par horloge en moyenne, ou plus dans des boucles bien réglées lorsque la mémoire n'est pas un goulot d'étranglement.

Ensuite, bien sûr, il y a plusieurs threads - exécutant plusieurs flux d'instructions sur (au moins logiquement) des processeurs/cœurs séparés.

Ainsi, un processeur moderne peut avoir, disons, 4 cœurs, chacun pouvant exécuter 2 multiplications vectorielles par horloge, et chacune de ces instructions peut fonctionner sur 8 opérandes. Ainsi, au moins en théorie, il peut effectuer 4 * 2 * 8 = 64 opérations par horloge.

Certaines instructions ont un débit meilleur ou pire. Par exemple, FP ajoute que le débit est inférieur à FMA ou multipliez sur Intel avant Skylake (1 vecteur par horloge au lieu de 2). Mais la logique booléenne comme AND ou XOR = a 3 vecteurs par débit d'horloge; il ne faut pas beaucoup de transistors pour construire une unité d'exécution AND/XOR/OR, donc les processeurs les répliquent. partie du noyau) sont courantes lors de l'utilisation d'instructions à haut débit, plutôt que de goulots d'étranglement sur une unité d'exécution spécifique.

39
Jerry Coffin

La vectorisation présente deux avantages principaux.

  1. Le principal avantage est que le matériel conçu pour prendre en charge les instructions vectorielles possède généralement un matériel capable d'exécuter plusieurs opérations ALU en général lorsque des instructions vectorielles sont utilisées. Par exemple, si vous lui demandez d'effectuer 16 ajouts avec une instruction vectorielle à 16 éléments, il peut avoir 16 additionneurs parallèles qui peuvent effectuer tous les ajouts à la fois. La manière niquement d'accéder à tous ces ajouteurs1 c'est par la vectorisation. Avec des instructions scalaires, vous obtenez juste le 1 additionneur solitaire.

  2. Il y a généralement des frais généraux enregistrés à l'aide d'instructions vectorielles. Vous chargez et stockez les données en gros morceaux (jusqu'à 512 bits à la fois sur certains processeurs Intel récents) et chaque itération de boucle fonctionne davantage, de sorte que la surcharge de la boucle est généralement plus faible dans un sens relatif2, et vous avez besoin de moins d'instructions pour effectuer le même travail, de sorte que la surcharge du processeur est plus faible, etc.

Enfin, votre dichotomie entre boucles et vectorisation est étrange. Lorsque vous prenez du code non vectoriel et le vectorisez, vous allez généralement vous retrouver avec une boucle s'il y en avait une auparavant, ou non s'il n'y en avait pas. La comparaison est vraiment entre les instructions scalaires (non vectorielles) et les instructions vectorielles.


1 Ou au moins 15 des 16, peut-être un est également utilisé pour effectuer des opérations scalaires.

2 Vous pourriez probablement obtenir un avantage de boucle supplémentaire dans le cas scalaire au prix de beaucoup de déroulement de boucle.

3
BeeOnRope

La vectorisation est un type de traitement parallèle. Il permet de consacrer plus de matériel informatique à l'exécution du calcul, de sorte que le calcul se fait plus rapidement.

De nombreux problèmes numériques, en particulier la solution d'équations aux dérivées partielles, nécessitent d'effectuer le même calcul pour un grand nombre de cellules, d'éléments ou de nœuds. La vectorisation effectue le calcul de nombreuses cellules/éléments/nœuds en parallèle.

La vectorisation utilise du matériel spécial. Contrairement à un processeur multicœur, pour lequel chacune des unités de traitement parallèle est un cœur de processeur entièrement fonctionnel, les unités de traitement vectoriel ne peuvent effectuer que des opérations simples, et toutes les unités effectuent la même opération en même temps, opérant sur une séquence de valeurs de données ( un vecteur) simultanément.

1
Raedwald