Je ne suis pas sûr à 100% que ce sera beaucoup mieux, mais vous pouvez essayer ce qui suit :
int count = 0;
#pragma omp parallel for
for(int i = 0; i < N; ++i) {
if(M[i]) {
#pragma omp atomic
T[count++] = i;
}
}
Si le tableau est assez clairsemé, les threads pourront parcourir un grand nombre de zéros sans attendre les autres. Mais vous ne pouvez mettre à jour qu'un seul indice à la fois. Le problème est que différents threads écrivent dans le même bloc de mémoire ( T
), ce qui signifie que vous rencontrerez des problèmes de mise en cache : chaque fois qu'un thread écrit dans le fichier T
En effet, le cache de tous les autres cœurs est "sale", si bien que lorsqu'ils essaient de le modifier, il se passe beaucoup de choses en coulisse. Tout cela est transparent pour vous (vous n'avez pas besoin d'écrire de code pour le gérer) mais cela ralentit considérablement les choses - je soupçonne que c'est là votre véritable goulot d'étranglement. Si votre matrice est suffisamment grande pour que cela en vaille la peine, vous pouvez essayer de faire ce qui suit :
- Créez autant de tableaux T qu'il y a de fils.
- Chaque thread met à jour sa propre version de T
- Combinez tous les tableaux T en un seul après la fin des boucles.
Cela pourrait être plus rapide (parce que les différents threads n'écrivent pas dans la même mémoire) - mais comme il y a si peu d'instructions dans la boucle, je pense que ce ne sera pas le cas.
EDITAR J'ai créé un programme de test complet, et j'ai constaté deux choses. Premièrement, le atomic
ne fonctionne pas dans toutes les versions de omp
et vous devrez peut-être utiliser T[count++] += i;
pour qu'il compile (ce qui n'est pas grave puisque T peut être mis à zéro au départ) ; plus troublant, vous n'obtiendrez pas deux fois la même réponse si vous faites cela (la valeur finale de count
change d'une passe à l'autre) ; si vous utilisez la fonction critical
ce n'est pas le cas.
Une deuxième observation est que la vitesse du programme ralentit vraiment quand on augmente le nombre de threads, ce qui confirme ce que je soupçonnais à propos de la mémoire partagée (temps pour 10M d'éléments traités) :
threads elapsed
1 0.09s
2 0.73s
3 1.21s
4 1.62s
5 2.34s
Vous pouvez voir que c'est vrai en changeant la façon dont la matrice clairsemée M
c'est que lorsque je crée M
comme un tableau aléatoire, et tester pour M[i] < 0.01 * RAND_MAX
(matrice dense à 0,1 %), les choses se déroulent beaucoup plus rapidement que si je la densifie à 10 % - ce qui montre que la partie située à l'intérieur de la matrice critical
La section nous ralentit vraiment.
Cela étant, je ne pense pas qu'il y ait un moyen d'accélérer cette tâche dans OMP - le travail de consolidation des sorties de tous les threads en une seule liste à la fin va simplement consommer tout avantage de vitesse que vous auriez pu avoir, étant donné le peu de choses qui se passent à l'intérieur de la boucle interne. Donc, plutôt que d'utiliser plusieurs threads, je vous suggère de réécrire la boucle de la manière la plus efficace possible - par exemple :
for( i = 0; i < N; i++) {
T[count] = i;
count += M[i];
}
Dans mon benchmark rapide, cette solution était plus rapide que la solution OMP - comparable au threads = 1
solution. Encore une fois, cela est dû à la façon dont on accède à la mémoire ici. Notez que j'évite d'utiliser un if
ce qui permet au code d'être aussi rapide que possible. Au lieu de cela, je profite du fait que M[i]
est toujours égal à zéro ou un. À la fin de la boucle, vous devez éliminer l'élément T[count]
parce qu'il sera invalide... les "bons éléments" sont T[0] ... T[count-1]
. Un tableau M
avec 10M d'éléments a été traité par cette boucle en ~ 0.02 sec sur ma machine. Cela devrait être suffisant pour la plupart des besoins ?