C ++ OpenMP: Scinde en boucle même en morceaux statiques et joint les données à la fin

J’essaie de créer une boucle for multithread en C ++ afin que le calcul soit divisé en plusieurs threads. Pourtant, il contient des données qui doivent être réunies dans l’ordre dans lequel elles se trouvent.

L’idée est donc d’abord de joindre les petits bits sur de nombreux cœurs (plus de 25 000 boucles), puis de joindre les données combinées une fois de plus à la fin.

std::vector ids; // mappings std::map combineData; // data per id myData outputData; // combined data based on the mappings myData threadData; // data per thread #pragma parallel for default(none) private(data, threadData) shared(combineData) for (int i=0; i<30000; i++) { threadData += combineData[ids[i]]; } // Then here I would like to get all the seperate thread data and combine them in a similar manner // Ie: for each threadData: outputData += threadData 

Quel serait le moyen efficace et bon d’aborder ceci?

Comment puis-je planifier la boucle openmp de sorte que la planification soit divisée de manière égale en morceaux

Par exemple pour 2 threads: [0, 1, 2, 3, 4, .., 14999] & [15000, 15001, 15002, 15003, 15004, .., 29999]

S’il existe un meilleur moyen de joindre les données (ce qui implique de réunir un grand nombre de std :: vecteurs et un peu de calcul masortingciel), tout en conservant l’ordre des pointeurs d’ajouts serait également utile.

Informations ajoutées

  • L’addition est associative, mais non commutative.
  • myData n’est pas un type insortingnsèque. C’est une classe contenant des données sous forme de plusieurs std :: vectors (et certaines données liées à l’API Autodesk Maya).
  • Chaque cycle effectue une multiplication de masortingce similaire en plusieurs points et les ajoute à un vecteur (en théorie, le temps de calcul devrait restr à peu près similaire pour chaque cycle).

Fondamentalement, il s’agit d’append des données de maillage (consistant en des vecteurs de données) (en combinant des maillages), bien que l’ordre de l’ensemble rend compte de la valeur d’index des sumts. L’index de sumt doit être cohérent et reconstructible.

Cela dépend de quelques propriétés de l’opérateur d’addition de myData . Si l’opérateur est à la fois associatif (A + B) + C = A + (B + C) ainsi que commutatif A + B = B + A vous pouvez utiliser une section critical ou si les données sont des données anciennes float, int, …) une reduction .

Cependant, si ce n’est pas commutatif comme vous le dites (l’ordre d’opération est important) mais toujours associatif, vous pouvez remplir un tableau avec un nombre d’éléments égal au nombre de threads des données combinées en parallèle, puis les fusionner dans l’ordre en série (voir plus bas). code ci-dessous. Utiliser schedule (statique) divisera les morceaux plus ou moins uniformément et avec le nombre de threads croissant, selon votre choix.

Si l’opérateur n’est ni associatif ni commutatif, je ne pense pas que vous puissiez le paralléliser (de manière efficace – par exemple, essayez de paralléliser efficacement une série de Fibonacci).

 std::vector ids; // mappings std::map combineData; // data per id myData outputData; // combined data based on the mappings myData *threadData; int nthreads; #pragma omp parallel { #pragma omp single { nthreads = omp_get_num_threads(); threadData = new myData[nthreads]; } myData tmp; #pragma omp for schedule(static) for (int i=0; i<30000; i++) { tmp += combineData[ids[i]]; } threadData[omp_get_thread_num()] = tmp; } for(int i=0; i 

Edit: Je ne suis pas sûr à 100% à ce stade si les morceaux seront atsortingbués dans l'ordre croissant du nombre de threads avec #pragma omp for schedule(static) (bien que je serais surpris de ne pas le faire). Une discussion est en cours sur cette question. Pendant ce temps, si vous voulez être sûr à 100% alors au lieu de

 #pragma omp for schedule(static) for (int i=0; i<30000; i++) { tmp += combineData[ids[i]]; } 

tu peux faire

 const int nthreads = omp_get_num_threads(); const int ithread = omp_get_thread_num(); const int start = ithread*30000/nthreads; const int finish = (ithread+1)*30000/nthreads; for(int i = start; i 

Modifier:

J'ai trouvé un moyen plus élégant de remplir en parallèle mais de fusionner dans l'ordre

 #pragma omp parallel { myData tmp; #pragma omp for schedule(static) nowait for (int i=0; i<30000; i++) { tmp += combineData[ids[i]]; } #pragma omp for schedule(static) ordered for(int i=0; i 

Cela évite d'allouer des données pour chaque thread ( threadData ) et de fusionner en dehors de la région parallèle.

Si vous voulez vraiment conserver le même ordre que dans le cas de la série, il n’y a pas d’autre moyen que de le faire en série. Dans ce cas, vous pouvez peut-être essayer de paralléliser les opérations effectuées dans l’ operator+= .

Si les opérations peuvent être effectuées de manière aléatoire, mais que la réduction des blocs a un ordre spécifique, il peut être intéressant d’examiner TBB parallel_reduce . Vous devrez écrire plus de code, mais si je me souviens bien, vous pouvez définir des opérations de réduction personnalisées complexes.

Si l’ordre des opérations n’a pas d’importance, votre extrait de code est presque terminé. Ce qui lui manque, c’est peut-être un critical pour agréger des données privées:

 std::vector ids; // mappings std::map combineData; // data per id myData outputData; // combined data based on the mappings #pragma omp parallel { myData threadData; // data per thread #pragma omp for nowait for (int ii =0; ii < total_iterations; ii++) { threadData += combineData[ids[ii]]; } #pragma omp critical { outputData += threadData; } #pragma omp barrier // From here on you are ensured that every thread sees // the correct value of outputData } 

La planification de la boucle for dans ce cas n’est pas importante pour la sémantique. Si la surcharge de l' operator+= est une opération relativement stable (en termes de temps nécessaire pour le calculer), vous pouvez utiliser schedule(static) qui répartit les itérations de manière égale entre les threads. Sinon, vous pouvez recourir à une autre planification pour équilibrer la charge de calcul (par exemple, une schedule(guided) ).

Enfin, si myData est une typedef de type insortingnsèque, vous pouvez alors éviter la section critique et utiliser une clause de reduction :

  #pragma omp for reduction(+:outputData) for (int ii =0; ii < total_iterations; ii++) { outputData += combineData[ids[ii]]; } 

Dans ce cas, vous n'avez pas besoin de déclarer explicitement quelque chose de privé.