Le standard OpenMP ne prend en compte que C++ 98 (ISO/IEC 14882: 1998). Cela signifie qu’il n’existe pas d’utilisation standard d’OpenMP prenant en charge C++ 03 ou même C++ 11. Ainsi, tout programme utilisant C++> 98 et OpenMP fonctionne en dehors des normes, ce qui implique que même s'il fonctionne dans certaines conditions, il est improbable qu'il soit portable, mais jamais garanti.
La situation est encore pire avec C++ 11 avec son propre support multi-threading, qui va très probablement entrer en conflit avec OpenMP pour certaines implémentations.
Dans quelle mesure est-il sûr d’utiliser OpenMP avec C++ 03 et C++ 11?
Peut-on utiliser en toute sécurité le multi-threading C++ 11 ainsi que OpenMP dans un même programme mais sans les entrelacer (c'est-à-dire aucune instruction OpenMP dans aucun code transmis à des fonctionnalités simultanées C++ 11 et aucune concurrence des tâches C++ 11 engendré par OpenMP)?
Je suis particulièrement intéressé par le cas où j'appelle d'abord du code utilisant OpenMP, puis un autre code utilisant la simultanéité C++ 11 sur les mêmes structures de données.
Walter, je crois que je vous ai non seulement informé de l’état actuel des choses dans cette autre discussion , mais que je vous ai également fourni des informations directement à partir de la source (c’est-à-dire de mon collègue membre du comité linguistique OpenMP).
OpenMP a été conçu comme une addition légère et parallèle de données à FORTRAN et à C, étendue ultérieurement aux idiomes C++ (par exemple, des boucles parallèles sur des itérateurs à accès aléatoire) et au parallélisme de tâches avec l'introduction de tâches explicites. Il est conçu pour être aussi portable portable sur autant de plates-formes que possible et pour fournir essentiellement les mêmes fonctionnalités dans les trois langues. Son modèle d'exécution est assez simple: une application à un seul thread crée des équipes de threads dans des régions parallèles, exécute des tâches de calcul à l'intérieur, puis rallie les équipes à l'exécution en série. Chaque thread d'une équipe parallèle peut créer ultérieurement sa propre équipe si le parallélisme imbriqué est activé.
Étant donné que OpenMP est principalement utilisé dans le calcul haute performance (après tout, son modèle de directive et d'exécution a été emprunté à High Performance Fortran), l'objectif principal de toute implémentation OpenMP est Efficience et non l'interopérabilité avec d'autres paradigmes de threading. . Sur certaines plates-formes, une mise en œuvre efficace ne peut être réalisée que si le moteur d'exécution OpenMP est le seul à pouvoir contrôler les threads de processus. De plus, certains aspects d'OpenMP risquent de ne pas bien fonctionner avec d'autres constructions de threading, par exemple la limite du nombre de threads définie par OMP_THREAD_LIMIT
lors de la conversion de plusieurs régions parallèles simultanées.
Étant donné que le standard OpenMP lui-même n'interdit pas formellement l'utilisation d'autres paradigmes de threading, mais ne standardise pas non plus l'interopérabilité avec de tels modèles, la prise en charge de telles fonctionnalités incombe aux développeurs. Cela signifie que certaines implémentations peuvent fournir une exécution simultanée sécurisée des régions OpenMP de niveau supérieur, d'autres non. Les développeurs x86 s’engagent à le prendre en charge, peut-être parce que la plupart d’entre eux sont également partisans d’autres modèles d’exécution (par exemple, Intel avec Cilk et TBB, GCC avec C++ 11, etc.) et que x86 est généralement considéré comme une plate-forme "expérimentale" ( les autres vendeurs sont généralement beaucoup plus conservateurs).
OpenMP 4.0 ne va pas plus loin que la norme ISO/IEC 14882: 1998 pour les fonctionnalités C++ qu’elle utilise (le brouillon SC12 est here ). La norme inclut désormais des éléments tels que l’affinité des fils portables - cela ne fonctionne certainement pas bien avec d’autres Les paradigmes de threading, qui pourraient fournir leurs propres mécanismes de liaison qui entrent en conflit avec ceux d'OpenMP. Une fois encore, le langage OpenMP est destiné à HPC (applications scientifiques et d'ingénierie parallèles de données et de tâches). Les constructions C++ 11 sont destinées à l'informatique à usage général Si vous voulez utiliser des outils simultanés sophistiqués comme C++ 11, utilisez uniquement C++ 11 ou si vous avez vraiment besoin de les combiner avec OpenMP, utilisez le sous-ensemble de fonctionnalités de langage C++ 98 si vous souhaitez rester portable. .
Je suis particulièrement intéressé par le cas où j'appelle d'abord du code utilisant OpenMP, puis un autre code utilisant l'accès simultané C++ 11 sur les mêmes structures de données.
Il n'y a pas de raisons évidentes pour que ce que vous voulez ne pas être possible, mais cela dépend de votre compilateur OpenMP et de son exécution. Il existe des bibliothèques gratuites et commerciales qui utilisent OpenMP pour une exécution parallèle (par exemple MKL), mais il existe toujours des avertissements (bien que parfois cachés profondément dans leurs manuels d’utilisation) d’éventuelles incompatibilités avec du code multithread qui donnent des informations sur ce qui est possible et quand. Comme toujours, cela sort du cadre du standard OpenMP et donc du YMMV.
Je suis effectivement intéressé par l'informatique haute performance, mais OpenMP (actuellement) ne me convient pas assez bien: il n'est pas assez flexible (mon algorithme n'est pas basé sur des boucles).
Peut-être cherchez-vous vraiment TBB ? Cela fournit un support pour le parallélisme basé sur les boucles et les tâches, ainsi que pour une variété de structures de données parallèles, en C++ standard, et est à la fois portable et à source ouverte.
(Clause de non-responsabilité: je travaille pour Intel, qui est très impliqué dans TBB, bien que je ne travaille pas réellement sur / TBB mais sur OpenMP :-); Je ne parle certainement pas pour Intel!).
Comme Jim Cownie, je suis également un employé d’Intel. Je suis d’accord avec lui pour dire que les blocs de construction Intel Threading (Intel TBB) pourraient constituer une bonne option, car ils offrent un parallélisme au niveau de la boucle comme OpenMP, mais aussi d’autres algorithmes parallèles, des conteneurs concurrents et des fonctionnalités de niveau inférieur. Et TBB essaie de suivre le standard C++ actuel.
Et pour clarifier pour Walter, Intel TBB inclut un algorithme parallel_reduce ainsi qu'un support de haut niveau pour les atomes et les mutex.
Le Guide de l'utilisateur Intel® Threading Building Block est disponible à l'adresse http://software.intel.com/sites/products/documentation/doclib/tbb_sa/help/tbb_userguide/title.htm Le Guide de l'utilisateur donne un aperçu de les fonctionnalités de la bibliothèque.
OpenMP est souvent (à ma connaissance, sans exception) implémenté au-dessus de Pthreads. Vous pouvez donc poser des questions sur l'interopérabilité en réfléchissant à la manière dont la concurrence de C++ 11 interagit avec le code Pthread.
Je ne sais pas si le sursouscription lié à l'utilisation de plusieurs modèles de thread est un problème pour vous, mais c'est certainement un problème pour OpenMP. Il existe une proposition pour résoudre ce problème dans OpenMP 5. Jusque-là, comment résoudre ceci est défini par la mise en œuvre. Ce sont des marteaux lourds, mais vous pouvez utiliser OMP_WAIT_POLICY
(OpenMP 4.5+), KMP_BLOCKTIME
(Intel et LLVM) et GOMP_SPINCOUNT
(GCC) pour résoudre ce problème. Je suis sûr que d'autres implémentations ont quelque chose de similaire.
L’un des problèmes pour lesquels l’interopérabilité pose un réel problème est celui de la coordination. le modèle de mémoire, c’est-à-dire comment se comportent les opérations atomiques. Ceci est actuellement indéfini, mais vous pouvez toujours raisonner à ce sujet. Par exemple, si vous utilisez des atomes atomiques C++ 11 avec le parallélisme OpenMP, vous devriez vous débrouiller, mais vous devez utiliser correctement les atomes atomiques C++ 11 à partir de threads OpenMP.
Mélanger Atomics OpenMP et C++ 11 est une mauvaise idée. Nous (le groupe de travail du comité de langue OpenMP chargé d'examiner la prise en charge du langage de base OpenMP 5) essayons actuellement de résoudre ce problème. Personnellement, je pense que les atomiques C++ 11 sont meilleurs que les atomiques OpenMP, je vous recommande donc d’utiliser C++ 11 (ou C11, ou __atomic
) et de laisser #pragma omp atomic
pour le Programmeurs Fortran.
Ci-dessous se trouve un exemple de code qui utilise C++ 11 atomics avec les threads OpenMP. Il fonctionne comme prévu partout où je l’ai testé.
Divulgation complète: Comme Jim et Mike, je travaille pour Intel :-)
#if defined(__cplusplus) && (__cplusplus >= 201103L)
#include <iostream>
#include <iomanip>
#include <atomic>
#include <chrono>
#ifdef _OPENMP
# include <omp.h>
#else
# error No OpenMP support!
#endif
#ifdef SEQUENTIAL_CONSISTENCY
auto load_model = std::memory_order_seq_cst;
auto store_model = std::memory_order_seq_cst;
#else
auto load_model = std::memory_order_acquire;
auto store_model = std::memory_order_release;
#endif
int main(int argc, char * argv[])
{
int nt = omp_get_max_threads();
#if 1
if (nt != 2) omp_set_num_threads(2);
#else
if (nt < 2) omp_set_num_threads(2);
if (nt % 2 != 0) omp_set_num_threads(nt-1);
#endif
int iterations = (argc>1) ? atoi(argv[1]) : 1000000;
std::cout << "thread ping-pong benchmark\n";
std::cout << "num threads = " << omp_get_max_threads() << "\n";
std::cout << "iterations = " << iterations << "\n";
#ifdef SEQUENTIAL_CONSISTENCY
std::cout << "memory model = " << "seq_cst";
#else
std::cout << "memory model = " << "acq-rel";
#endif
std::cout << std::endl;
std::atomic<int> left_ready = {-1};
std::atomic<int> right_ready = {-1};
int left_payload = 0;
int right_payload = 0;
#pragma omp parallel
{
int me = omp_get_thread_num();
/// 0=left 1=right
bool parity = (me % 2 == 0);
int junk = 0;
/// START TIME
#pragma omp barrier
std::chrono::high_resolution_clock::time_point t0 = std::chrono::high_resolution_clock::now();
for (int i=0; i<iterations; ++i) {
if (parity) {
/// send to left
left_payload = i;
left_ready.store(i, store_model);
/// recv from right
while (i != right_ready.load(load_model));
//std::cout << i << ": left received " << right_payload << std::endl;
junk += right_payload;
} else {
/// recv from left
while (i != left_ready.load(load_model));
//std::cout << i << ": right received " << left_payload << std::endl;
junk += left_payload;
///send to right
right_payload = i;
right_ready.store(i, store_model);
}
}
/// STOP TIME
#pragma omp barrier
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
/// PRINT TIME
std::chrono::duration<double> dt = std::chrono::duration_cast<std::chrono::duration<double>>(t1-t0);
#pragma omp critical
{
std::cout << "total time elapsed = " << dt.count() << "\n";
std::cout << "time per iteration = " << dt.count()/iterations << "\n";
std::cout << junk << std::endl;
}
}
return 0;
}
#else // C++11
#error You need C++11 for this test!
#endif // C++11
OpenMP 5.0 définit maintenant l’interaction vers C++ 11. Mais généralement, utiliser quelque chose à partir de C++ 11 et plus loin "peut entraîner un comportement non spécifié" .
Cette spécification API OpenMP fait référence à ISO/IEC 14882: 2011 en tant que C++ 11. Bien que les futures versions de la spécification OpenMP soient censées prendre en charge les fonctionnalités suivantes, leur utilisation actuelle peut entraîner un comportement non spécifié.