2
votes

OpenMp: Comment créer des dimensions std :: vector threadprivate

Je suis nouveau dans le monde OpenMp et j'ai une erreur que je ne peux pas corriger. Le code original est bien trop gros, j'ai donc créé un petit code pour résumer le problème:

J'ai un std :: vector plus dimensionnel (2D et 3D), qui ne devrait pas être partagé les fils. Si je les marque comme privés, ils provoquent toujours des erreurs de mémoire, car les threads les partagent toujours.

J'ai trouvé une solution à ce problème: J'ai créé une dimension supplémentaire pour le vecteur 2d, afin que chaque thread puisse accéder à sa propre copie:

#include <iostream>
#include <omp.h>
#include <vector>

//this should represent my problem(without my fix)
int main(){
        std::vector < std::vector < int > > v;
    v.resize(3);

    #pragma omp parallel for num_threads(2) private(v)
    for(int i = 0; i < 10; i++){
            v[1].push_back(i); 
    }
    return 0;
}

Je sais que ce n'est pas une solution intelligente pour mon problème, mais maintenant chaque thread a obtenu sa propre copie du 2d Vector. Maintenant vient la partie étrange: cela provoque toujours des plantages de mémoire parfois si je ne mets pas #pragma omp critial devant. Je ne comprends pas vraiment pourquoi cela est nécessaire, car les threads ne doivent jamais accéder à la même mémoire.

myVector[omp_get_thread_num()][1].push_back(i);

J'espère qu'il existe une meilleure solution pour rendre mon thread vectoriel 2D privé. p>

ps. il n'est pas possible d'allouer le vecteur à l'intérieur de la partie omp.


7 commentaires

Bienvenue dans stackoverflow! Pouvez-vous mettre en évidence quel est votre problème exactement


Mon problème: même si je "marque" mon vecteur v comme privé. tous les threads en partagent la même copie. Ils accéderont donc à la même mémoire et provoqueront des erreurs de mémoire. Cela fonctionne bien pour le vecteur 1d, mais avec un vecteur 2d, le private (v) semble n'avoir aucun effet


Dans votre code, les vecteurs privés v dans une section parallèle sont construits par défaut . Par conséquent, ils sont vides et v [1] provoque un comportement indéfini (probablement présenté comme une erreur de mémoire).


Je sais que ce n'est pas une solution intelligente à mon problème. Pourquoi pas? Si vous avez besoin, par exemple, d'accéder à ces vecteurs après la fin d'une section parallèle, c'est parfaitement bien. Je me soucierais simplement du faux partage , car dans la dimension la plus extérieure, les objets vectoriels de threads individuels sont placés les uns à côté des autres en mémoire. Leur modification (par exemple, changement de taille ou de capacité) pourrait alors souffrir d'un faux partage.


Merci, que dois-je faire pour éviter le faux partage? Je ne sais pas quelle sera la taille des vecteurs à la fin, donc ils augmentent la taille et la capacité plusieurs fois.


@ Rapha167 Cela dépend. Vous ne fournissez pas suffisamment de détails. Par exemple, avez-vous besoin que tous les vecteurs de threads existent après la fin d'une section parallèle? Si c'est le cas, vous pouvez les mettre dans une liste au lieu d'un vecteur . Avec une liste, un faux partage est moins probable. Une autre possibilité est de ne mettre dans un vecteur que des pointeurs vers des vecteurs et de les créer dynamiquement.


@Daniel Langr Non, le vecteur n'a pas besoin d'exister après la région parallèle. C'est un vecteur 2D, qui a une taille / capacité inconnue pendant la fonction, mais qui est vide à la fin. Votre solution consistant à ne placer que des pointeurs dans le vecteur est quelque chose qui m'est venu à l'esprit auparavant. Peut-être que je peux essayer ça


3 Réponses :


0
votes

Vous pouvez utiliser le spécificateur de stockage thread_local:

int main(){
   thread_local std::vector < std::vector < int > > v;
   // ...
}


2 commentaires

Il n'est généralement pas conseillé de mélanger les concepts de threading C ++ (comme le spécificateur de stockage thread_local ) avec OpenMP.


Merci Zulan, c'est bon à savoir. Existe-t-il un "moyen mp ouvert" de résoudre le problème?



0
votes

Vous ne spécifiez pas beaucoup de détails importants sur votre code d'origine. Cependant, une façon pourrait être de créer d'abord une section parallèle et de définir des vecteurs privés à l'intérieur, puis de paralléliser la boucle. Pour votre code exemplaire, il pourrait ressembler à:

int main() {
   #pramga omp parallel
   {
      std::vector<std::vector<int>> v;
      v.resize(3);

      #pragma omp for 
      for (int i = 0; i < 10; i++)
         v[1].push_back(i); 
   }
}


0 commentaires

2
votes

Vous devez comprendre que les variables provenant d'un périmètre extérieur et déclarées private fonctionnent comme si elles étaient déclarées localement sans initialiseur . Donc, chaque copie locale est un vecteur vide, donc votre code ne peut pas fonctionner.

En général, il est préférable avec OpenMP de déclarer des variables privées localement - de cette façon vous évitez beaucoup de confusion entre la "valeur extérieure" et "à l'intérieur valeurs privées "qui ne sont pas du tout liées. Vous pouvez le faire en fractionnant les directives parallel et pour .

myVector[omp_get_thread_num()].push_back(); // Bad performance
myVector[omp_get_thread_num()][1].push_back(i); // Ok

Notez que v code > n'est pas disponible après la région parallèle - c'est bien! Dans votre exemple d'origine, v est disponible après la région parallèle - mais sa valeur n'a rien à voir avec la valeur des threads à l'intérieur.

Si vous avez besoin de conserver les informations de v , vous voudrez peut-être examiner la réduction, mais cela dépend de votre cas d'utilisation spécifique.

Votre approche de myVector [omp_get_thread_num ()] approche naïve. Ce code est correct , mais dans tous les cas où vous modifiez les valeurs du vecteur le plus extérieur , il a de mauvaises performances en raison d'un faux partage.

#pragma omp parallel
{
    std::vector<std::vector<int>> v;
    v.resize(3);
    #pragma omp for
    for(int i = 0; i < 10; i++){
        v[1].push_back(i); 
    }
}

Il est donc généralement conseillé de ne pas faire cela et d'utiliser à la place des variables déclarées localement. Néanmoins, si votre code plante, il y a autre chose qui ne va pas. Dans ce cas, vous devez préparer un exemple minimal reproductible et poser une deuxième question (en vous référant à ceci). p>

Maintenant threadprivate est quelque chose de différent de private . private est généralement ce que vous voulez et fait référence à la tâche / portée spécifique. Dans la plupart des cas, vous n'avez pas besoin ou ne voulez pas de threadprivate .


2 commentaires

cette réponse m'a beaucoup aidé, merci! Comme je n'ai pas besoin de v après la région parallèle, je vais essayer votre solution. Mon problème est que v est déclaré dans un fichier d'en-tête. Donc, si je le change et le déclare dans une région parallèle, je dois passer un pointeur vers le vecteur vers BEAUCOUP de fonctions. Mais comme vous l'avez mentionné, si je veux une bonne accélération, je n'ai pas d'autre choix


Les variables globales avec des threads ne se mélangent pas bien. Je recommande fortement d'étendre vos interfaces. Si vous devez vraiment ... appliquer une directive threadprivate à la variable globale peut fonctionner. Mais il est beaucoup plus difficile de raisonner - et je ne peux pas vous recommander comment faire car je ne connais pas assez bien votre application.