OpenACC Tutorial - Profiling/fr: Difference between revisions

From Alliance Doc
Jump to navigation Jump to search
No edit summary
No edit summary
 
(57 intermediate revisions by 2 users not shown)
Line 5: Line 5:
|content=
|content=
* comprendre ce qu'est un profileur
* comprendre ce qu'est un profileur
* savoir utiliser PGPROF
* savoir utiliser le profileur NVPROF
* comprendre la performance du code  
* comprendre la performance du code  
* savoir concentrer vos efforts et réécrire les routines qui exigent beaucoup de temps
* savoir concentrer vos efforts et réécrire les routines qui exigent beaucoup de temps
Line 13: Line 13:
* comment le temps est employé aux points critiques (''hotspots''),
* comment le temps est employé aux points critiques (''hotspots''),
* comprendre la performance du code,
* comprendre la performance du code,
* savoir comment mieux employer votre temps.
* savoir comment mieux employer votre temps de développement.


Pourquoi est-ce important de connaitre les points critiques dans le code?  
Pourquoi est-ce important de connaitre les points critiques dans le code?  
D'après la loi d'Amdahl, paralléliser les routines qui exigent le plus de temps d'exécution (les points critiques) produit le plus d'impact.
D'après [https://en.wikipedia.org/wiki/Amdahl%27s_law la loi d'Amdahl], paralléliser les routines qui exigent le plus de temps d'exécution (les points critiques) produit le plus d'impact.


== Préparer le code pour l'exercice ==
== Préparer le code pour l'exercice ==
Pour notre exemple, nous utilisons du code provenant de  [https://github.com/calculquebec/cq-formation-openacc ces dépôts]. Téléchargez les fichiers et utilisez les répertoires ''cpp'' ou ''f90''. Le but de l'exercice est de compiler et lier le code et d'obtenir un exécutable que nous profilerons.
Pour l'exemple suivant, nous utilisons du code provenant de  [https://github.com/calculquebec/cq-formation-ce dépôt de données Git].
[https://github.com/calculquebec/cq-formation-openacc/archive/refs/heads/main.zip Téléchargez et faites l'extraction du paquet] et positionnez-vous dans le répertoire <code>cpp</code> ou <code>f90</code>. Le but de cet exemple est de compiler et lier le code pour obtenir un exécutable pour en profiler le code source avec un profileur.
 
{{Callout
{{Callout
|title=Choix du compilateur
|title=Choix du compilateur
|content=
|content=
En date de mai 2016, relativement peu de compilateurs offraient les fonctionnalités d'OpenACC. Les plus avancés en ce sens sont les compilateurs du [http://www.pgroup.com/ Portland Group] de [http://www.nvidia.com/content/global/global.php NVidia] et ceux de [http://www.cray.com/ Cray]. Pour ce est qui de [https://gcc.gnu.org/wiki/OpenACC GNU], l'implémentation d'OpenACC dans la version 5 était expérimentale et devrait être complète dans la version 6.  
Mis de l'avant par [https://www.cray.com/ Cray] et par [https://www.nvidia.com NVIDIA] via sa division
[https://www.pgroup.com/support/release_archive.php Portland Group] jusqu'en 2020 puis via  [https://developer.nvidia.com/hpc-sdk sa trousse HPC SDK], ceux deux types de compilateurs offrent le support le plus avancé pour OpenACC.


Dans ce tutoriel, nous utilisons la version 16.3 des [http://www.pgroup.com/support/download_pgi2016.php?view=current compilateurs du Portland Group] qui sont gratuits pour des fins de recherche universitaire.  
Quant aux [https://gcc.gnu.org/wiki/OpenACC compilateurs GNU], le support pour OpenACC 2.x continue de s'améliorer depuis la version 6 de GCC.
En date de juillet 2022, les versions GCC 10, 11 et 12 supportent la version 2.6 d'OpenACC.
 
Dans ce tutoriel, nous utilisons la version 22.7 de [https://developer.nvidia.com/nvidia-hpc-sdk-releases NVIDIA HPC SDK]. Notez que les compilateurs NVIDIA sont gratuits à des fins de recherche universitaire.  
}}
 
{{Command
|module load nvhpc/22.7
|result=
Lmod is automatically replacing "intel/2020.1.217" with "nvhpc/22.7".
 
The following have been reloaded with a version change:
  1) gcccore/.9.3.0 => gcccore/.11.3.0        3) openmpi/4.0.3 => openmpi/4.1.4
  2) libfabric/1.10.1 => libfabric/1.15.1    4) ucx/1.8.0 => ucx/1.12.1
}}
}}


Line 31: Line 47:
|make  
|make  
|result=
|result=
pgc++ -fast  -c -o main.o main.cpp
nvc++   -c -o main.o main.cpp
"vector.h", line 30: warning: variable "vcoefs" was declared but never
nvc++ main.o -o cg.x
      referenced
      double *vcoefs=v.coefs;
                    ^
 
pgc++ main.o -o cg.x -fast
}}
}}


Une fois l'exécutable créé, nous allons profiler le code.
Une fois l'exécutable <code>cg.x</code> créé, nous allons profiler son code source. Le profileur mesure les appels des fonctions en exécutant et en surveillant ce programme.
'''Important :''' Cet exécutable utilise environ 3Go de mémoire et un cœur CPU presque à 100&nbsp;%. '''L'environnement de test devrait donc avoir 4Go de mémoire disponible et au moins deux (2) cœurs CPU'''.


{{Callout
{{Callout
|title=Choix du profileur
|title=Choix du profileur
|content=
|content=
Dans ce tutoriel, nous utilisons plusieurs des profileurs suivants&nbsp;:  
Dans ce tutoriel, nous utilisons deux profileurs&nbsp;:  
* PGPROF : outil simple mais puissant pour l'analyse de programmes parallèles écrits avec OpenMP, OpenACC ou [https://fr.wikipedia.org/wiki/Compute_Unified_Device_Architecture CUDA]; rappelons que PGPROF est gratuit pour des fins de recherche universitaire.
 
* NVVP (NVIDIA Visual Profiler) : outil d'analyse multiplateforme pour des programmes écrits avec OpenACC et CUDA C/C++.
* '''[https://docs.nvidia.com/cuda/profiler-users-guide/ <code>nvprof</code> de NVIDIA]''' , un profileur en ligne de commande capable d'analyser des codes non GPU
* NVPROF : version ligne de commande du NVIDIA Visual Profiler.
* '''[[OpenACC_Tutorial_-_Adding_directives#NVIDIA_Visual_Profiler|<code>nvvp</code> (NVIDIA Visual Profiler) ]]''', un outil d'analyse multiplateforme pour des programmes écrits avec OpenACC et CUDA C/C++.
Puisque <code>cg.x</code> que nous avons construit n'utilise pas encore le GPU, nous allons commencer l'analyse avec le profileur <code>nvprof</code>.
}}
}}


=== PGPROF  ===
=== Profileur en ligne de commande NVIDIA <code>nvprof</code> ===
[[File:Pgprof new0.png|thumbnail|300px|Commencer une nouvelle session PGPROF|right  ]]
Dans sa trousse de développement pour le calcul de haute performance, NVIDIA fournit habituellement <code>nvprof</code>, mais la version qu'il faut utiliser sur nos grappes est incluse dans un module CUDA.
<br />
{{Command
<br />
|module load cuda/11.7
<br />
}}
Ouvrez d'abord une nouvelle session PGPROF.
Localisez ensuite le fichier exécutable du code que vous voulez profiler.
Enfin, sélectionnez les options;  par exemple, pour profiler l'activité du processeur, cochez ''Profile execution of the CPU''.
<br />
<br />
<br />
<br />
<br />


=== NVVP ===
Pour profiler un exécutable CPU pur, nous devons ajouter les arguments <code>--cpu-profiling on</code> à la ligne de commande.
 
Le NVIDIA Visual Profiler peut être employé avec les applications OpenACC. C'est un outil d'analyse multiplateforme pour les instructions OpenACC et CUDA C/C++.
[[File:Nvvp-pic1.png|thumbnail|300px|Localisez l'exécutable à profiler|left]]
[[File:Nvvp-pic0.png|thumbnail|300px|Profileur NVVP|right  ]]
<br />
<br />
<br />
<br />
<br />
 
=== NVPROF ligne de commande ===
La version ligne de commande de NVPROF est semblable à GPU prof.
{{Command
{{Command
|nvprof --cpu-profiling on ./cgi.x  
|nvprof --cpu-profiling on ./cg.x  
|result=
|result=
...
<Program output >
<Program output >
...
======== CPU profiling result (bottom up):
======== CPU profiling result (bottom up):
84.25% matvec(matrix const &, vector const &, vector const &)
Time(%)      Time  Name
84.25% main
83.54% 90.6757s  matvec(matrix const &, vector const &, vector const &)
9.50% waxpby(double, vector const &, double, vector const &, vector const &)
83.54% 90.6757s  {{!}} main
3.37% dot(vector const &, vector const &)
  7.94% 8.62146s  waxpby(double, vector const &, double, vector const &, vector const &)
2.76% allocate_3d_poisson_matrix(matrix&, int)
  7.94%  8.62146s  {{!}} main
2.76% main
  5.86% 6.36584s  dot(vector const &, vector const &)
0.11% __c_mset8
  5.86%  6.36584s  {{!}} main
0.03% munmap
  2.47% 2.67666s  allocate_3d_poisson_matrix(matrix&, int)
  0.03% free_matrix(matrix&)
  2.47% 2.67666s  {{!}} main
    0.03% main
  0.13% 140.35ms  initialize_vector(vector&, double)
  0.13% 140.35ms  {{!}} main
...
======== Data collected at 100Hz frequency
======== Data collected at 100Hz frequency
}}
}}
Dans le résultat, la fonction <code>matvec()</code> utilise 83.5&nbsp;% du temps d'exécution; son appel se trouve dans la fonction <code>main()</code>.
==Renseignements sur le compilateur==
==Renseignements sur le compilateur==
Avant de travailler sur la routine, nous devons comprendre ce que fait le compilateur; posons-nous les questions suivantes&nbsp;:
Avant de travailler sur la routine, nous devons comprendre ce que fait le compilateur; posons-nous les questions suivantes&nbsp;:
* Quelles sont les optimisations qui ont été appliquées?  
* Quelles sont les optimisations qui ont été automatiquement appliquées par le compilateur?  
* Qu'est-ce qui a empêché d'optimiser davantage?
* Qu'est-ce qui a empêché d'optimiser davantage?
* La performance serait-elle affectée par les petites modifications?
* La performance serait-elle affectée par les petites modifications?


Le compilateur PGI offre l'indicateur '''-Minfo'''  avec les options suivantes&nbsp;:
Le compilateur NVIDIA offre l'indicateur <code>-Minfo</code> avec les options suivantes&nbsp;:
* accel – liste des opérations du compilateur relativement à l'accélérateur
* <code>all</code>, pour imprimer presque tous les types d'information, incluant
* all – résultats en sortie du compilateur
** <code>accel</code> pour les opérations du compilateur en rapport avec l'accélérateur
* intensity – renseignements sur l'intensité de la boucle
** <code>inline</code> pour l'information sur les fonctions extraites et alignées
* ccff – ajout de renseignements aux fichiers objet pour utilisation future
** <code>loop,mp,par,stdpar,vect</code> pour les renseignements sur l'optimisation et la vectorisation des boucles
* <code>intensity</code>, pour imprimer l'information sur l'intensité des boucles
* (aucune option) produit le même résultat que l'option  <code>all</code>, mais sans l'information fournie par <code>inline</code>.


== Obtenir les renseignements sur le compilateur  ==
== Obtenir les renseignements sur le compilateur  ==
* Éditez le Makefile.
* Modifiez le Makefile.
CXX=pgc++
  CXX=nvc++
CXXFLAGS=-fast -Minfo=all,intensity,ccff LDFLAGS=${CXXFLAGS}
  CXXFLAGS=-fast -Minfo=all,intensity
  LDFLAGS=${CXXFLAGS}
 
* Effectuez un nouveau build.
* Effectuez un nouveau build.
{{Command
{{Command
|make
|make clean; make
|result=
|result=
pgc++ CXXFLAGS=-fast -Minfo=all,intensity,ccff LDFLAGS=-fast -fast   -c -o main.o main.cpp
...
"vector.h", line 30: warning: variable "vcoefs" was declared but never
nvc++ -fast -Minfo=all,intensity  -c -o main.o main.cpp
          referenced
initialize_vector(vector &, double):
    double *vcoefs=v.coefs;
    20, include "vector.h"
            ^
           36, Intensity = 0.0
 
_Z17initialize_vectorR6vectord:
           37, Intensity = 0.0
               Memory set idiom, loop replaced by call to __c_mset8
               Memory set idiom, loop replaced by call to __c_mset8
_Z3dotRK6vectorS1_:
dot(const vector &, const vector &):
           27, Intensity = 1.00  
    21, include "vector_functions.h"
              Generated 3 alternate versions of the loop
           27, Intensity = 1.00
               Generated vector sse code for the loop
               Generated vector simd code for the loop containing reductions
              Generated 2 prefetch instructions for the loop
          28, FMA (fused multiply-add) instruction(s) generated
_Z6waxpbydRK6vectordS1_S1_:
waxpby(double, const vector &, double, const vector &, const vector &):
           39, Intensity = 1.00  
    21, include "vector_functions.h"
           39, Intensity = 1.00
               Loop not vectorized: data dependency
               Loop not vectorized: data dependency
               Loop unrolled 4 times
              Generated vector simd code for the loop
_Z26allocate_3d_poisson_matrixR6matrixi:
               Loop unrolled 2 times
              FMA (fused multiply-add) instruction(s) generated
          40, FMA (fused multiply-add) instruction(s) generated
allocate_3d_poisson_matrix(matrix &, int):
    22, include "matrix.h"
           43, Intensity = 0.0
           43, Intensity = 0.0
              Loop not fused: different loop trip count
           44, Intensity = 0.0
           44, Intensity = 0.0
               Loop not vectorized/parallelized: loop count too small
               Loop not vectorized/parallelized: loop count too small
Line 142: Line 148:
           59, Intensity = 0.0
           59, Intensity = 0.0
               Loop not vectorized: data dependency
               Loop not vectorized: data dependency
_Z6matvecRK6matrixRK6vectorS4_:
matvec(const matrix &, const vector &, const vector &):
    23, include "matrix_functions.h"
           29, Intensity = (num_rows*((row_end-row_start)*        2))/(num_rows+(num_rows+(num_rows+((row_end-row_start)+(row_end-row_start)))))
           29, Intensity = (num_rows*((row_end-row_start)*        2))/(num_rows+(num_rows+(num_rows+((row_end-row_start)+(row_end-row_start)))))
           33, Intensity = 1.00  
           33, Intensity = 1.00
              Unrolled inner loop 4 times
               Generated vector simd code for the loop containing reductions
               Generated 2 prefetch instructions for the loop
          37, FMA (fused multiply-add) instruction(s) generated
main:
main:
     61, Intensity = 16.00 
     38, allocate_3d_poisson_matrix(matrix &, int) inlined, size=41 (inline) file main.cpp (29)
        Loop not vectorized/parallelized: potential early exits
          43, Intensity = 0.0
pgc++ CXXFLAGS=-fast -Minfo=all,intensity,ccff LDFLAGS=-fast main.o -o cg.x -fast
              Loop not fused: different loop trip count
          44, Intensity = 0.0
              Loop not vectorized/parallelized: loop count too small
          45, Intensity = 0.0
              Loop unrolled 3 times (completely unrolled)
          57, Intensity = 0.0
              Loop not fused: function call before adjacent loop
          59, Intensity = 0.0
              Loop not vectorized: data dependency
    42, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
    43, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
    44, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
    45, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
    46, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
    48, initialize_vector(vector &, double) inlined, size=5 (inline) file main.cpp (34)
          36, Intensity = 0.0
              Memory set idiom, loop replaced by call to __c_mset8
    49, initialize_vector(vector &, double) inlined, size=5 (inline) file main.cpp (34)
          36, Intensity = 0.0
              Memory set idiom, loop replaced by call to __c_mset8
    52, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.0
              Memory copy idiom, loop replaced by call to __c_mcopy8
    53, matvec(const matrix &, const vector &, const vector &) inlined, size=19 (inline) file main.cpp (20)
          29, Intensity = [symbolic], and not printable, try the -Mpfi -Mpfo options
              Loop not fused: different loop trip count
          33, Intensity = 1.00
              Generated vector simd code for the loop containing reductions
    54, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          27, FMA (fused multiply-add) instruction(s) generated
          36, FMA (fused multiply-add) instruction(s) generated
          39, Intensity = 0.67
              Loop not fused: different loop trip count
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
              FMA (fused multiply-add) instruction(s) generated
    56, dot(const vector &, const vector &) inlined, size=9 (inline) file main.cpp (21)
          27, Intensity = 1.00
              Loop not fused: function call before adjacent loop
              Generated vector simd code for the loop containing reductions
    61, Intensity = 0.0
    62, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.0
              Memory copy idiom, loop replaced by call to __c_mcopy8
    65, dot(const vector &, const vector &) inlined, size=9 (inline) file main.cpp (21)
          27, Intensity = 1.00
              Loop not fused: different controlling conditions
              Generated vector simd code for the loop containing reductions
    67, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.67
              Loop not fused: different loop trip count
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
    72, matvec(const matrix &, const vector &, const vector &) inlined, size=19 (inline) file main.cpp (20)
          29, Intensity = [symbolic], and not printable, try the -Mpfi -Mpfo options
              Loop not fused: different loop trip count
          33, Intensity = 1.00
              Generated vector simd code for the loop containing reductions
    73, dot(const vector &, const vector &) inlined, size=9 (inline) file main.cpp (21)
          27, Intensity = 1.00
              Loop not fused: different loop trip count
              Generated vector simd code for the loop containing reductions
    77, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.67
              Loop not fused: different loop trip count
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
    78, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.67
              Loop not fused: function call before adjacent loop
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
    88, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
    89, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
    90, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
    91, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
    92, free_matrix(matrix &) inlined, size=5 (inline) file main.cpp (73)
}}
}}
== Intensité computationnelle  ==
L'intensité computationnelle d'une boucle représente la quantité de travail accompli par la boucle en fonction des opérations effectuées en mémoire.


'''intensité computationnelle = opérations de calcul / opérations en mémoire'''
== Interpréter le résultat  ==
L'''intensité computationnelle'' d'une boucle représente la quantité de travail accompli par la boucle en fonction des opérations effectuées en mémoire, soit
 
<math>\mbox{intensité computationnelle} = \frac{\mbox{opérations de calcul}}{\mbox{opérations en mémoire}}</math>


Une valeur de 1 ou plus indique que la boucle serait bien exécutée sur un processeur graphique (GPU).
Dans le résultat, une valeur supérieure à 1 pour <code>Intensity</code> indique que la boucle serait bien exécutée sur un processeur graphique (GPU).


== Comprendre le code  ==
== Comprendre le code  ==
Regardons attentivement le code suivant &nbsp;:
Regardons attentivement la boucle principale  de
<syntaxhighlight lang="cpp" line highlight="1,5,10,12">
[https://github.com/calculquebec/cq-formation-openacc/blob/main/cpp/matrix_functions.h#L29 la fonction <code>matvec()</code> implémentée dans <code>matrix_functions.h</code>]:
for(int i=0;i<num_rows;i++) {
<syntaxhighlight lang="cpp" line start="29" highlight="1,5,10,12">
  double sum=0;
  for(int i=0;i<num_rows;i++) {
  int row_start=row_offsets[i];
    double sum=0;
  int row_end=row_offsets[i+1];
    int row_start=row_offsets[i];
  for(int j=row_start; j<row_end;j++) {
    int row_end=row_offsets[i+1];
    unsigned int Acol=cols[j];
    for(int j=row_start; j<row_end;j++) {
    double Acoef=Acoefs[j];  
      unsigned int Acol=cols[j];
    double xcoef=xcoefs[Acol];  
      double Acoef=Acoefs[j];  
    sum+=Acoef*xcoef;
      double xcoef=xcoefs[Acol];  
      sum+=Acoef*xcoef;
    }
    ycoefs[i]=sum;
   }
   }
  ycoefs[i]=sum;
}
</syntaxhighlight>  
</syntaxhighlight>  
On trouvera les dépendances de données en se posant les questions suivantes&nbsp;:
On trouvera les dépendances de données en se posant les questions suivantes&nbsp;:
* Une itération en affecte-t-elle d'autres?
* Une itération en affecte-t-elle d'autres?
* Les itérations lisent-elles ou écrivent-elles à des endroits différents du même tableau?
** par exemple, quand une  '''[https://fr.wikipedia.org/wiki/Suite_de_Fibonacci suite de Fibonacci]''' est générée, chaque nouvelle valeur dépend des deux valeurs qui la précèdent. Il est donc très difficile, sinon impossible, d'implémenter un parallélisme efficace.
* Est-ce que sum est une dépendance? Non, c'est une réduction.
* L'accumulation des valeurs dans <code>sum</code> est-elle une dépendance?
** Non, c'est une''' [https://en.wikipedia.org/wiki/Reduction_operator réduction]'''! Et les compilateurs modernes optimisent bien ce genre de réduction.
* Est-ce que les itérations de boucle écrivent et lisent dans les mêmes vecteurs de sorte que les valeurs sont utilisées ou écrasées par d'autres itérations?
** Heureusement, ceci ne se produit pas dans le code ci-dessus.


[https://docs.computecanada.ca/wiki/OpenACC_Tutorial_-_Adding_directives/fr^ Page suivante, Ajouter des directives]
Maintenant que le code est analysé, nous pouvons ajouter des directives au compilateur.


[https://docs.computecanada.ca/wiki/OpenACC_Tutorial/fr Retour au début du tutoriel]
[[OpenACC Tutorial - Introduction/fr|<- Page précédente, ''Introduction'']] | [[OpenACC Tutorial/fr|^- Retour au début du tutoriel]] | [[OpenACC Tutorial - Adding directives/fr|Page suivante, ''Ajouter des directives'' ->]]

Latest revision as of 18:16, 9 May 2023

Other languages:


Objectifs d'apprentissage
  • comprendre ce qu'est un profileur
  • savoir utiliser le profileur NVPROF
  • comprendre la performance du code
  • savoir concentrer vos efforts et réécrire les routines qui exigent beaucoup de temps


Profiler du code

Pourquoi auriez-vous besoin de profiler du code? Parce que c'est la seule façon de comprendre

  • comment le temps est employé aux points critiques (hotspots),
  • comprendre la performance du code,
  • savoir comment mieux employer votre temps de développement.

Pourquoi est-ce important de connaitre les points critiques dans le code? D'après la loi d'Amdahl, paralléliser les routines qui exigent le plus de temps d'exécution (les points critiques) produit le plus d'impact.

Préparer le code pour l'exercice

Pour l'exemple suivant, nous utilisons du code provenant de dépôt de données Git. Téléchargez et faites l'extraction du paquet et positionnez-vous dans le répertoire cpp ou f90. Le but de cet exemple est de compiler et lier le code pour obtenir un exécutable pour en profiler le code source avec un profileur.


Choix du compilateur

Mis de l'avant par Cray et par NVIDIA via sa division Portland Group jusqu'en 2020 puis via sa trousse HPC SDK, ceux deux types de compilateurs offrent le support le plus avancé pour OpenACC.

Quant aux compilateurs GNU, le support pour OpenACC 2.x continue de s'améliorer depuis la version 6 de GCC. En date de juillet 2022, les versions GCC 10, 11 et 12 supportent la version 2.6 d'OpenACC.

Dans ce tutoriel, nous utilisons la version 22.7 de NVIDIA HPC SDK. Notez que les compilateurs NVIDIA sont gratuits à des fins de recherche universitaire.


Question.png
[name@server ~]$ module load nvhpc/22.7
Lmod is automatically replacing "intel/2020.1.217" with "nvhpc/22.7".

The following have been reloaded with a version change:
  1) gcccore/.9.3.0 => gcccore/.11.3.0        3) openmpi/4.0.3 => openmpi/4.1.4
  2) libfabric/1.10.1 => libfabric/1.15.1     4) ucx/1.8.0 => ucx/1.12.1
Question.png
[name@server ~]$ make 
nvc++    -c -o main.o main.cpp
nvc++ main.o -o cg.x

Une fois l'exécutable cg.x créé, nous allons profiler son code source. Le profileur mesure les appels des fonctions en exécutant et en surveillant ce programme. Important : Cet exécutable utilise environ 3Go de mémoire et un cœur CPU presque à 100 %. L'environnement de test devrait donc avoir 4Go de mémoire disponible et au moins deux (2) cœurs CPU.


Choix du profileur

Dans ce tutoriel, nous utilisons deux profileurs :

Puisque cg.x que nous avons construit n'utilise pas encore le GPU, nous allons commencer l'analyse avec le profileur nvprof.


Profileur en ligne de commande NVIDIA nvprof

Dans sa trousse de développement pour le calcul de haute performance, NVIDIA fournit habituellement nvprof, mais la version qu'il faut utiliser sur nos grappes est incluse dans un module CUDA.

Question.png
[name@server ~]$ module load cuda/11.7

Pour profiler un exécutable CPU pur, nous devons ajouter les arguments --cpu-profiling on à la ligne de commande.

Question.png
[name@server ~]$ nvprof --cpu-profiling on ./cg.x 
...
<Program output >
...
======== CPU profiling result (bottom up):
Time(%)      Time  Name
 83.54%  90.6757s  matvec(matrix const &, vector const &, vector const &)
 83.54%  90.6757s  | main
  7.94%  8.62146s  waxpby(double, vector const &, double, vector const &, vector const &)
  7.94%  8.62146s  | main
  5.86%  6.36584s  dot(vector const &, vector const &)
  5.86%  6.36584s  | main
  2.47%  2.67666s  allocate_3d_poisson_matrix(matrix&, int)
  2.47%  2.67666s  | main
  0.13%  140.35ms  initialize_vector(vector&, double)
  0.13%  140.35ms  | main
...
======== Data collected at 100Hz frequency

Dans le résultat, la fonction matvec() utilise 83.5 % du temps d'exécution; son appel se trouve dans la fonction main().

Renseignements sur le compilateur

Avant de travailler sur la routine, nous devons comprendre ce que fait le compilateur; posons-nous les questions suivantes :

  • Quelles sont les optimisations qui ont été automatiquement appliquées par le compilateur?
  • Qu'est-ce qui a empêché d'optimiser davantage?
  • La performance serait-elle affectée par les petites modifications?

Le compilateur NVIDIA offre l'indicateur -Minfo avec les options suivantes :

  • all, pour imprimer presque tous les types d'information, incluant
    • accel pour les opérations du compilateur en rapport avec l'accélérateur
    • inline pour l'information sur les fonctions extraites et alignées
    • loop,mp,par,stdpar,vect pour les renseignements sur l'optimisation et la vectorisation des boucles
  • intensity, pour imprimer l'information sur l'intensité des boucles
  • (aucune option) produit le même résultat que l'option all, mais sans l'information fournie par inline.

Obtenir les renseignements sur le compilateur

  • Modifiez le Makefile.
 CXX=nvc++
 CXXFLAGS=-fast -Minfo=all,intensity
 LDFLAGS=${CXXFLAGS}
  • Effectuez un nouveau build.
Question.png
[name@server ~]$ make clean; make
...
nvc++ -fast -Minfo=all,intensity   -c -o main.o main.cpp
initialize_vector(vector &, double):
     20, include "vector.h"
          36, Intensity = 0.0
              Memory set idiom, loop replaced by call to __c_mset8
dot(const vector &, const vector &):
     21, include "vector_functions.h"
          27, Intensity = 1.00
              Generated vector simd code for the loop containing reductions
          28, FMA (fused multiply-add) instruction(s) generated
waxpby(double, const vector &, double, const vector &, const vector &):
     21, include "vector_functions.h"
          39, Intensity = 1.00
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 2 times
              FMA (fused multiply-add) instruction(s) generated
          40, FMA (fused multiply-add) instruction(s) generated
allocate_3d_poisson_matrix(matrix &, int):
     22, include "matrix.h"
          43, Intensity = 0.0
              Loop not fused: different loop trip count
          44, Intensity = 0.0
              Loop not vectorized/parallelized: loop count too small
          45, Intensity = 0.0
              Loop unrolled 3 times (completely unrolled)
          57, Intensity = 0.0
          59, Intensity = 0.0
              Loop not vectorized: data dependency
matvec(const matrix &, const vector &, const vector &):
     23, include "matrix_functions.h"
          29, Intensity = (num_rows*((row_end-row_start)*         2))/(num_rows+(num_rows+(num_rows+((row_end-row_start)+(row_end-row_start)))))
          33, Intensity = 1.00
              Generated vector simd code for the loop containing reductions
          37, FMA (fused multiply-add) instruction(s) generated
main:
     38, allocate_3d_poisson_matrix(matrix &, int) inlined, size=41 (inline) file main.cpp (29)
          43, Intensity = 0.0
              Loop not fused: different loop trip count
          44, Intensity = 0.0
              Loop not vectorized/parallelized: loop count too small
          45, Intensity = 0.0
              Loop unrolled 3 times (completely unrolled)
          57, Intensity = 0.0
              Loop not fused: function call before adjacent loop
          59, Intensity = 0.0
              Loop not vectorized: data dependency
     42, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
     43, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
     44, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
     45, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
     46, allocate_vector(vector &, unsigned int) inlined, size=3 (inline) file main.cpp (24)
     48, initialize_vector(vector &, double) inlined, size=5 (inline) file main.cpp (34)
          36, Intensity = 0.0
              Memory set idiom, loop replaced by call to __c_mset8
     49, initialize_vector(vector &, double) inlined, size=5 (inline) file main.cpp (34)
          36, Intensity = 0.0
              Memory set idiom, loop replaced by call to __c_mset8
     52, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.0
              Memory copy idiom, loop replaced by call to __c_mcopy8
     53, matvec(const matrix &, const vector &, const vector &) inlined, size=19 (inline) file main.cpp (20)
          29, Intensity = [symbolic], and not printable, try the -Mpfi -Mpfo options
              Loop not fused: different loop trip count
          33, Intensity = 1.00
              Generated vector simd code for the loop containing reductions
     54, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          27, FMA (fused multiply-add) instruction(s) generated
          36, FMA (fused multiply-add) instruction(s) generated
          39, Intensity = 0.67
              Loop not fused: different loop trip count
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
              FMA (fused multiply-add) instruction(s) generated
     56, dot(const vector &, const vector &) inlined, size=9 (inline) file main.cpp (21)
          27, Intensity = 1.00
              Loop not fused: function call before adjacent loop
              Generated vector simd code for the loop containing reductions
     61, Intensity = 0.0
     62, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.0
              Memory copy idiom, loop replaced by call to __c_mcopy8
     65, dot(const vector &, const vector &) inlined, size=9 (inline) file main.cpp (21)
          27, Intensity = 1.00
              Loop not fused: different controlling conditions
              Generated vector simd code for the loop containing reductions
     67, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.67
              Loop not fused: different loop trip count
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
     72, matvec(const matrix &, const vector &, const vector &) inlined, size=19 (inline) file main.cpp (20)
          29, Intensity = [symbolic], and not printable, try the -Mpfi -Mpfo options
              Loop not fused: different loop trip count
          33, Intensity = 1.00
              Generated vector simd code for the loop containing reductions
     73, dot(const vector &, const vector &) inlined, size=9 (inline) file main.cpp (21)
          27, Intensity = 1.00
              Loop not fused: different loop trip count
              Generated vector simd code for the loop containing reductions
     77, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.67
              Loop not fused: different loop trip count
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
     78, waxpby(double, const vector &, double, const vector &, const vector &) inlined, size=10 (inline) file main.cpp (33)
          39, Intensity = 0.67
              Loop not fused: function call before adjacent loop
              Loop not vectorized: data dependency
              Generated vector simd code for the loop
              Loop unrolled 4 times
     88, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
     89, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
     90, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
     91, free_vector(vector &) inlined, size=2 (inline) file main.cpp (29)
     92, free_matrix(matrix &) inlined, size=5 (inline) file main.cpp (73)

Interpréter le résultat

L'intensité computationnelle d'une boucle représente la quantité de travail accompli par la boucle en fonction des opérations effectuées en mémoire, soit

Dans le résultat, une valeur supérieure à 1 pour Intensity indique que la boucle serait bien exécutée sur un processeur graphique (GPU).

Comprendre le code

Regardons attentivement la boucle principale de la fonction matvec() implémentée dans matrix_functions.h:

  for(int i=0;i<num_rows;i++) {
    double sum=0;
    int row_start=row_offsets[i];
    int row_end=row_offsets[i+1];
    for(int j=row_start; j<row_end;j++) {
      unsigned int Acol=cols[j];
      double Acoef=Acoefs[j]; 
      double xcoef=xcoefs[Acol]; 
      sum+=Acoef*xcoef;
    }
    ycoefs[i]=sum;
  }

On trouvera les dépendances de données en se posant les questions suivantes :

  • Une itération en affecte-t-elle d'autres?
    • par exemple, quand une suite de Fibonacci est générée, chaque nouvelle valeur dépend des deux valeurs qui la précèdent. Il est donc très difficile, sinon impossible, d'implémenter un parallélisme efficace.
  • L'accumulation des valeurs dans sum est-elle une dépendance?
    • Non, c'est une réduction! Et les compilateurs modernes optimisent bien ce genre de réduction.
  • Est-ce que les itérations de boucle écrivent et lisent dans les mêmes vecteurs de sorte que les valeurs sont utilisées ou écrasées par d'autres itérations?
    • Heureusement, ceci ne se produit pas dans le code ci-dessus.

Maintenant que le code est analysé, nous pouvons ajouter des directives au compilateur.

<- Page précédente, Introduction | ^- Retour au début du tutoriel | Page suivante, Ajouter des directives ->