web-dev-qa-db-fra.com

La divergence entre les branches est-elle vraiment si mauvaise?

J'ai vu de nombreuses questions disséminées sur Internet à propos de la divergence entre les branches et comment l'éviter . Cependant, même après avoir lu des dizaines d'articles sur le fonctionnement de CUDA, je n'arrive pas à voir comment éviter la divergence de branche aide dans la plupart des cas. Avant que quelqu'un ne me saute dessus avec les griffes tendues, permettez-moi de décrire ce que je considère être "la plupart des cas".

Il me semble que la plupart des cas de divergence de branche impliquent un certain nombre de blocs de code vraiment distincts. Par exemple, nous avons le scénario suivant:

if (A):
  foo(A)
else:
  bar(B)

Si nous avons deux threads qui rencontrent cette divergence, le thread 1 s'exécutera en premier, en prenant le chemin A. Ensuite, le thread 2 prendra le chemin B. Afin de supprimer la divergence, nous pourrions changer le bloc ci-dessus pour lire comme ceci:

foo(A)
bar(B)

En supposant qu'il soit sûr d'appeler foo(A) sur le thread 2 et bar(B) sur le thread 1, on peut s'attendre à une amélioration des performances. Cependant, voici comment je le vois:

Dans le premier cas, les threads 1 et 2 s'exécutent en série. Appelez cela deux cycles d'horloge.

Dans le second cas, les threads 1 et 2 exécutent foo(A) en parallèle, puis exécutent bar(B) en parallèle. Cela me ressemble toujours à deux cycles d'horloge, la différence est que dans le premier cas, si foo(A) implique une lecture de la mémoire, j'imagine que le thread 2 peut commencer l'exécution pendant cette latence, ce qui entraîne la dissimulation de la latence. Si tel est le cas, le code divergent de branche est plus rapide.

33
longbowrocks

Vous supposez (au moins c'est l'exemple que vous donnez et la seule référence que vous faites) que la seule façon d'éviter la divergence de branche est de permettre à tous les threads d'exécuter tout le code.

Dans ce cas, je conviens qu'il n'y a pas beaucoup de différence.

Mais éviter la divergence de branche a probablement plus à voir avec la restructuration de l'algorithme à un niveau supérieur qu'à l'ajout ou la suppression de certaines instructions if et à rendre le code "sûr" à exécuter dans tous les threads.

Je vais vous donner un exemple. Supposons que je sache que les threads impairs devront gérer le composant bleu d'un pixel et même les threads devront gérer le composant vert:

#define N 2 // number of pixel components
#define BLUE 0
#define GREEN 1
// pixel order: px0BL px0GR px1BL px1GR ...


if (threadIdx.x & 1)  foo(pixel(N*threadIdx.x+BLUE));
else                  bar(pixel(N*threadIdx.x+GREEN));

Cela signifie que chaque thread alternatif prend un chemin donné, que ce soit foo ou bar. Alors maintenant, ma chaîne prend deux fois plus de temps à exécuter.

Cependant, si je réorganise mes données de pixels de sorte que les composantes de couleur soient contiguës, peut-être par blocs de 32 pixels: BL0 BL1 BL2 ... GR0 GR1 GR2 ...

Je peux écrire un code similaire:

if (threadIdx.x & 32)  foo(pixel(threadIdx.x));
else                   bar(pixel(threadIdx.x));

Il semble toujours que j'ai la possibilité de divergence. Mais comme la divergence se produit sur les limites de la chaîne, une chaîne donne exécute le chemin if ou le chemin else, donc aucune divergence réelle ne se produit.

C'est un exemple trivial, et probablement stupide, mais il illustre qu'il peut y avoir des moyens de contourner la divergence de distorsion qui n'impliquent pas l'exécution de tout le code de tous les chemins divergents.

46
Robert Crovella