Extension SIMD à C ++ OpenMP dans Visual Studio

À l'ère des applications d'IA omniprésentes, il y a une demande émergente du compilateur accélérant le code d'apprentissage machine intensif en calcul pour le matériel existant. Un tel code effectue généralement le calcul mathématique comme la transformation et la manipulation de matrice et il est généralement sous la forme de boucles. L'extension SIMD d'OpenMP offre aux utilisateurs un moyen sans effort d'accélérer les boucles en exploitant explicitement l'unité vectorielle des processeurs modernes. Nous sommes fiers de commencer à proposer la vectorisation SIMD C / C ++ OpenMP dans Visual Studio 2019.


L'interface du programme d'application OpenMP C / C ++ a été initialement conçue pour améliorer les performances des applications en permettant au code d'être efficacement exécuté en parallèle sur plusieurs processeurs dans les années 1990. Au fil des ans, la norme OpenMP a été étendue pour prendre en charge des concepts supplémentaires tels que la parallélisation basée sur les tâches, la vectorisation SIMD et le déchargement de processeur. Depuis 2005, Visual Studio prend en charge la norme OpenMP 2.0 qui se concentre sur la parallélisation multithread. Alors que le monde entre dans une ère de l'IA, nous voyons une opportunité croissante d'améliorer la qualité du code en étendant la prise en charge de la norme OpenMP dans Visual Studio. Nous continuons notre voyage dans Visual Studio 2019 en ajoutant la prise en charge d'OpenMP SIMD.




OpenMP SIMD, introduit pour la première fois dans la norme OpenMP 4.0, cible principalement la vectorisation de boucle. Il s'agit à ce jour de la fonctionnalité OpenMP la plus utilisée dans l'apprentissage automatique selon nos recherches. En annotant une boucle avec une directive OpenMP SIMD, le compilateur peut ignorer les dépendances vectorielles et vectoriser la boucle autant que possible. Le compilateur respecte l'intention des utilisateurs d'exécuter simultanément plusieurs itérations de boucle.


#pragma omp simd for (i = 0; i < count; i++) { a[i] = b[i] + 1; } 

Comme vous le savez peut-être, C ++ dans Visual Studio fournit déjà des pragmas de boucle non OpenMP similaires comme #pragma vector et #pragma ivdep . Cependant, le compilateur peut faire plus avec OpenMP SIMD. Par exemple:


  1. Le compilateur est toujours autorisé à ignorer toutes les dépendances vectorielles présentes.
  2. / fp: fast est activé dans la boucle.
  3. Les boucles avec appels de fonction sont vectorisables.
  4. Les boucles externes sont vectorisables.
  5. Les boucles imbriquées peuvent être fusionnées en une seule boucle et vectorisées.
  6. L'accélération hybride est réalisable avec #pragma omp for simd pour permettre le multithreading à gros grain et la vectorisation à grain fin.

De plus, la directive OpenMP SIMD peut prendre les clauses suivantes pour améliorer encore la vectorisation:


  • simdlen ( longueur ): spécifiez le nombre de voies vectorielles
  • safelen ( longueur ): spécifiez la distance de dépendance du vecteur
  • linear ( list [ : linear-step] ): le mappage linéaire de la variable d'induction de boucle à l'abonnement au tableau
  • aligné ( liste [ : alignement] ): l'alignement des données
  • privé ( liste ): spécifier la privatisation des données
  • lastprivate ( liste ): spécifiez la privatisation des données avec la valeur finale de la dernière itération
  • réduction ( réduction-identifiant : liste ): spécifiez des opérations de réduction personnalisées
  • effondrement ( n ): imbrication de boucle coalescente

Nouveau -openmp: commutateur expérimental


Un programme annoté OpenMP-SIMD peut être compilé avec un nouveau commutateur CL -openmp: experimental. Ce nouveau commutateur active des fonctionnalités OpenMP supplémentaires non disponibles sous -openmp . Bien que le nom de ce commutateur soit «expérimental», le commutateur lui-même et les fonctionnalités qu'il active sont entièrement pris en charge et prêts pour la production. Le nom indique qu'il n'active aucun sous-ensemble ou version complète d'une norme OpenMP. Les futures itérations du compilateur pourraient utiliser ce commutateur pour activer des fonctionnalités OpenMP supplémentaires et de nouveaux commutateurs liés à OpenMP pourraient être ajoutés. Le commutateur -openmp: experimental résume le commutateur -openmp , ce qui signifie qu'il est compatible avec toutes les fonctionnalités d'OpenMP 2.0. Notez que la directive SIMD et ses clauses ne peuvent pas être compilées avec le commutateur -openmp .


Pour les boucles qui ne sont pas vectorisées, le compilateur émettra un message pour chacune d'entre elles comme ci-dessous. Par exemple,


cl -O2 -openmp: expérimental mycode.cpp


mycode.cpp (84): info C5002: boucle simd Omp non vectorisée pour la raison '1200'


mycode.cpp (90): info C5002: boucle simd Omp non vectorisée pour la raison '1200'


Pour les boucles vectorisées, le compilateur reste silencieux sauf si un commutateur de journalisation de vectorisation est fourni:


cl -O2 -openmp: expérimental -Qvec-report: 2 mycode.cpp


mycode.cpp (84): info C5002: boucle simd Omp non vectorisée pour la raison '1200'


mycode.cpp (90): info C5002: boucle simd Omp non vectorisée pour la raison '1200'


mycode.cpp (96): info C5001: boucle simd Omp vectorisée


Comme première étape de la prise en charge d'OpenMP SIMD, nous avons fondamentalement branché le pragma SIMD avec le vectoriseur backend sous le nouveau commutateur. Nous nous sommes concentrés sur la vectorisation des boucles les plus internes en améliorant l'analyse du vectoriseur et de l'alias. Aucune des clauses SIMD n'est effective dans Visual Studio 2019 au moment de la rédaction de cet article. Ils seront analysés mais ignorés par le compilateur avec un avertissement émis pour la sensibilisation de l'utilisateur. Par exemple, le compilateur émettra


avertissement C4849: clause "simdlen" d'OpenMP ignorée dans la directive "simd"


pour le code suivant:


 #pragma omp simd simdlen(8) for (i = 1; i < count; i++) { a[i] = a[i-1] + 1; b[i] = *c + 1; bar(i); } 

En savoir plus sur la sémantique de la directive OpenMP SIMD


La directive OpenMP SIMD fournit aux utilisateurs un moyen de dicter au compilateur de vectoriser une boucle. Le compilateur est autorisé à ignorer la légalité apparente d'une telle vectorisation en acceptant la promesse d'exactitude des utilisateurs. Il incombe aux utilisateurs lorsqu'un comportement inattendu se produit avec la vectorisation. En annotant une boucle avec la directive OpenMP SIMD, les utilisateurs ont l'intention d'exécuter plusieurs itérations de boucle simultanément. Cela donne au compilateur beaucoup de liberté pour générer du code machine qui tire parti des ressources SIMD ou vectorielles sur le processeur cible. Bien que le compilateur ne soit pas responsable de l'exploration de l'exactitude et du profit d'un tel parallélisme spécifié par l'utilisateur, il doit toujours garantir le comportement séquentiel d'une itération à boucle unique.


Par exemple, la boucle suivante est annotée avec la directive OpenMP SIMD. Il n'y a pas de parallélisme parfait entre les itérations de boucle car il y a une dépendance en arrière d'un [i] à un [i-1]. Mais en raison de la directive SIMD, le compilateur est toujours autorisé à regrouper les itérations consécutives de la première instruction dans une instruction vectorielle et à les exécuter en parallèle.


 #pragma omp simd for (i = 1; i < count; i++) { a[i] = a[i-1] + 1; b[i] = *c + 1; bar(i); } 

Par conséquent, la forme vectorielle transformée suivante de la boucle est légale car le compilateur conserve le comportement séquentiel de chaque itération de boucle d'origine. En d'autres termes, a [i] est exécuté après a [-1], b [i] est après a [i] et l'appel à la barre se produit enfin.


 #pragma omp simd for (i = 1; i < count; i+=4) { a[i:i+3] = a[i-1:i+2] + 1; b[i:i+3] = *c + 1; bar(i); bar(i+1); bar(i+2); bar(i+3); } 

Il est interdit de déplacer la référence de mémoire * c hors de la boucle si elle peut alias avec a [i] ou b [i] . Il est également illégal de réorganiser les instructions dans une seule itération d'origine si elle rompt la dépendance séquentielle. Par exemple, la boucle transformée suivante n'est pas légale.


 c = b; t = *c; #pragma omp simd for (i = 1; i < count; i+=4) { a[i:i+3] = a[i-1:i+2] + 1; bar(i); // illegal to reorder if bar[i] depends on b[i] b[i:i+3] = t + 1; // illegal to move *c out of the loop bar(i+1); bar(i+2); bar(i+3); } 

Plans futurs et rétroaction


Nous vous encourageons à essayer cette nouvelle fonctionnalité. Comme toujours, nous apprécions vos commentaires. Si vous voyez une boucle OpenMP SIMD que vous prévoyez d'être vectorisée, mais pas ou si le code généré n'est pas optimal, veuillez nous le faire savoir. Nous pouvons être contactés via les commentaires ci-dessous, par e-mail ( visualcpp@microsoft.com ), twitter (@visualc) ou via la communauté des développeurs .


À l'avenir, nous aimerions connaître votre besoin de fonctionnalités OpenMP manquantes dans Visual Studio. Comme il y a eu plusieurs évolutions majeures dans OpenMP depuis la norme 2.0, OpenMP a maintenant des fonctionnalités énormes pour faciliter vos efforts pour créer des programmes hautes performances. Par exemple, la programmation simultanée basée sur les tâches est disponible à partir d'OpenMP 3.0. L'informatique hétérogène (CPU + accélérateurs) est prise en charge dans OpenMP 4.0. La vectorisation SIMD avancée et la prise en charge de la parallélisation de boucle DOACROSS sont également disponibles dans la dernière norme OpenMP. Veuillez consulter les révisions standard complètes et les jeux de fonctionnalités sur le site officiel d'OpenMP: https://www.openmp.org . Nous vous demandons sincèrement votre avis sur les fonctionnalités OpenMP spécifiques que vous aimeriez voir. Nous aimerions également savoir comment vous utilisez OpenMP pour accélérer votre code. Vos commentaires sont essentiels car ils aideront à orienter la prise en charge d'OpenMP dans Visual Studio.


Avatar
Hongtao Yu

Source: https://habr.com/ru/post/fr446688/


All Articles