1. Introduction au C++



sommaire chapitre 1

1.11. Débogage, profilage, optimisation

Le processus de développement d'un programme passe par trois étapes

1.11.1. Débogage

1.11.1.a  Electric Fence

Electric Fence helps you detect two common programming bugs:



Installer le paquet Electric-Fence qui permet de traquer les erreurs d'accès à la mémoire :

sudo apt-get install electric-fence

Compiler ensuite le programme en réalisant l'édition de liens avec Electric Fence :

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4.  
  5. int main() {
  6.     char *arr;    
  7.    
  8.     arr = (char *) malloc(sizeof(char)*5);
  9.     strcpy(arr,"amee is my name");
  10.     return 0;
  11. }
  12.  
  13.  
gcc -c efence.c -ggdb
gcc -o efence.exe efence.o -ggdb -lefence

1.11.1.b  Utilisation d'un débogueur

Il existe plusieurs débogueurs sous Linux :

Compiler ensuite le programme avec -ggdb :

gcc -o my.exe my_program.o -ggdb 

Puis lancer ddd sur l'exécutable :

ddd my.exe  

ddd

1.11.2. Profilage

Dans la suite de cette section, nous étudions le comportement du programme suivant :

  1. /* ===================================================
  2.  * compile with gcc :
  3.  *   gcc -pg -o prog.exe prog.c
  4.  *   ./prog.exe
  5.  *   gprof prog.exe
  6.  * =================================================== */
  7.  
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <time.h>
  11.  
  12. // number of loops for each calculation
  13. #define MAX_LOOPS 2000
  14.  
  15. // first dimension of arrays (number of rows)
  16. #define SIZE1 1000
  17. // second dimension of arrays (number of columns)
  18. #define SIZE2 2000
  19.  
  20. // type of array
  21. #define TYPE float
  22.  
  23. /**
  24.  * allocate matrix of size : SIZE1*SIZE2*sizeof(TYPE)
  25.  */
  26. TYPE **allocate_array() {
  27.   int i;
  28.   TYPE **array;
  29.  
  30.   array=(TYPE **) calloc(SIZE1,sizeof(TYPE *));
  31.   for (i=0;i<SIZE1;++i) {
  32.     array[i]=(TYPE*) calloc(SIZE2,sizeof(TYPE));
  33.   }
  34.   return array;
  35. }
  36.  
  37. /**
  38.  * fill matrix with random values
  39.  */
  40. void fill_array(TYPE **a) {
  41.   int i, j;
  42.  
  43.   for (i=0;i<SIZE1;++i) {
  44.     for (j=0;j<SIZE2;++j) {
  45.       a[i][j] = rand() % 1000;
  46.     }
  47.   }
  48. }
  49.  
  50. /**
  51.  * function that is called
  52.  * MAX_LOOPS * SIZE1 / 2 times by proc21
  53.  * MAX_LOOPS * SIZE1 / 2 times by proc21
  54.  *
  55.  */
  56. void proc3(TYPE *c, TYPE *a, TYPE *b, int k) {
  57.   int j;
  58.  
  59.   for (j=0;j<SIZE2;++j) {
  60.     c[j]=a[j]*k+b[j];
  61.   }
  62. }
  63.  
  64. /**
  65.  * first calculation
  66.  */
  67. void proc21(TYPE **a, TYPE **b) {
  68.   int i;
  69.  
  70.   for (i=0;i<SIZE1;i+=2) {
  71.     proc3(a[i+1],a[i],b[i],3);
  72.   }
  73. }
  74.  
  75. /**
  76.  * second calculation
  77.  */
  78. void proc22(TYPE **a, TYPE **b) {
  79.   int i;
  80.  
  81.   for (i=0;i<SIZE1;i+=2) {
  82.     proc3(b[i+1],a[i],b[i],-5);
  83.   }
  84. }
  85.  
  86.  
  87. /**
  88.  * function that calls
  89.  * MAX_LOOPS times proc21
  90.  * MAX_LOOPS times proc22
  91.  */
  92. void proc1(TYPE **a, TYPE **b) {
  93.   int i;
  94.  
  95.   for (i=0; i<MAX_LOOPS; ++i)
  96.     proc21(a,b);
  97.   for (i=0; i<MAX_LOOPS; ++i)
  98.     proc22(a,b);
  99.  
  100. }
  101.  
  102.  
  103. int main() {
  104.   TYPE **a1, **a2;
  105.  
  106.   srand(time(NULL));
  107.  
  108.   // allocate matrices
  109.   a1 = allocate_array();
  110.   a2 = allocate_array();
  111.  
  112.   // fill matrices
  113.   fill_array(a1);
  114.   fill_array(a2);
  115.  
  116.   // call main function for computations
  117.   proc1(a1,a2);
  118.  
  119.   return 0;
  120. }
  121.  
  122.  

1.11.2.a  Utilisation de prof/gprof sous Unix

On utilse les options de compilation classiques avec gprof :

> gcc -o prog.exe prog.c -pg
> ./prog.exe
> gprof prog.exe >prog_gprof.txt

Le résultat en sortie peut être visualisé ici.

a) profil à plat

Le flat profile est le suivant :

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
 99.55     21.94    21.94  2000000     0.00     0.00  proc3
  0.27     22.00     0.06        2     0.03     0.03  fill_array
  0.09     22.02     0.02     2000     0.00     0.01  proc21
  0.09     22.04     0.02     2000     0.00     0.01  proc22
  0.00     22.04     0.00        2     0.00     0.00  allocate_array
  0.00     22.04     0.00        1     0.00    21.98  proc1

On peut donc lire que :

b) granularité

Le second type d'information reporté par gprof concerne la granularité, c'est à dire le temps total passé dans chaque sous-programme ainsi que le nombre d'appels aux autres sous-programmes :


index % time    self  children    called     name
                                                 
[1]    100.0    0.00   22.04                 main [1]
                0.00   21.98       1/1           proc1 [2]
                0.06    0.00       2/2           fill_array [6]
                0.00    0.00       2/2           allocate_array [7]
-----------------------------------------------
                0.00   21.98       1/1           main [1]
[2]     99.7    0.00   21.98       1         proc1 [2]
                0.02   10.97    2000/2000        proc21 [4]
                0.02   10.97    2000/2000        proc22 [5]
-----------------------------------------------
               10.97    0.00 1000000/2000000     proc21 [4]
               10.97    0.00 1000000/2000000     proc22 [5]
[3]     99.5   21.94    0.00 2000000         proc3 [3]
-----------------------------------------------
                0.02   10.97    2000/2000        proc1 [2]
[4]     49.9    0.02   10.97    2000         proc21 [4]
               10.97    0.00 1000000/2000000     proc3 [3]
-----------------------------------------------
                0.02   10.97    2000/2000        proc1 [2]
[5]     49.9    0.02   10.97    2000         proc22 [5]
               10.97    0.00 1000000/2000000     proc3 [3]
-----------------------------------------------
                0.06    0.00       2/2           main [1]
[6]      0.3    0.06    0.00       2         fill_array [6]
-----------------------------------------------
                0.00    0.00       2/2           main [1]
[7]      0.0    0.00    0.00       2         allocate_array [7]
-----------------------------------------------

On voit donc que :

1.11.2.b  Utilisation de VTune Analyzer (Intel)

VTune est un outil complet de profiling et de tuning qui permet de déterminer les hot spots et d'éclairer sur leur comportement : instruction peu performante, accès mémoire non aligné, nombre important de défauts de cache dus à des données de trop grande taille.

1.11.2.c  cas de prog.c

1.11.2.d  Utilisation de Valgrind

valgrind est un ensemble d'utilitaires qui servent à débuguer et profiler les programmes

Valgrind is an instrumentation framework for building dynamic analysis tools. There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. You can also use Valgrind to build new tools.

The Valgrind distribution currently includes six production-quality tools:

It also includes three experimental tools: a stack/global array overrun detector, a second heap profiler that examines how heap blocks are used, and a SimPoint basic block vector generator

Voir également dans la partie C++11 l'utilisation de valgrind avec l'outil memcheck ainsi que ce tutoriel.

On compile le programme à étudier avec l'option de débugage -g puis il suffit de lancer le profilage. Attention cependant car certaines options peuvent augmenter le temps d'exécution d'un facteur 20 à 100 par rapport à une exécution normale.

Dans le cas de l'outil callgrind (équivalent de gprof), on tapera au niveau du shell :

valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --collect-jumps=yes <executable> [args...]

Les options --dump-instr=yes --simulate-cache=yes ou --collect-jumps=yes peuvent être supprimées afin de gagner du temps.

Valgrind
Utilisation de kcachegrind pour visualiser les résultats
en sortie de valgrind

Pour obtenir le visuel du call-graph (il faut avoir installé graphviz -- graph visualization software -- sous Ubuntu : sudo apt-get install graphviz), il suffit de cliquer en bas à droite sur l'onglet "Call-graph".

Si seule une partie du code vous intéresse, vous pouvez faire en sorte que valgrind ne se déclenche qu'à partir de l'appel d'un sous-programme, comme sur l'exemple suivant :

  1. #include <valgrind/callgrind.h>
  2.  
  3. int main() {
  4.     code_with_no_instrumentation();
  5.  
  6.     CALLGRIND_START_INSTRUMENTATION;
  7.     CALLGRIND_TOGGLE_COLLECT;
  8.    
  9.     code_to_analyze();
  10.    
  11.     CALLGRIND_TOGGLE_COLLECT;
  12.     CALLGRIND_STOP_INSTRUMENTATION;
  13.  
  14.     code_with_no_instrumentation();
  15.  
  16.     exit(EXIT_SUCCESS);  
  17. }
  18.  

Il faudra alors relancer valgrind avec les options --collect-at-start=no et --instru-at-start=no :

valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --collect-jumps=yes --collect-at-start=no --instru-at-start=no <executable> [args...]

1.11.3. Optimisation

L'optimisation est parfois difficile à réaliser et demande beaucoup d'efforts afin d'améliorer sensiblement le code (cf. Cours L3 Informatique). Certaines techniques sont à utiliser :

Cependant, les options de compilation des compilateurs permettent parfois d'obtenir un gain substantiel. Par exemple sous gcc/c++ :

Sous Linux, pour connaître les différentes technologies disponibles sur un processeur il suffit de taper la commande suivante :

cat /proc/cpuinfo | grep "model name" | uniq ; cat /proc/cpuinfo | grep -E "flags" | uniq

Voici un exemple de sortie pour un processeur Intel Xeon E5-2670 v2 (Q3'13, 10 cores + 10 threads, 25 Mo L3, Turbo Freq. 3.3 Ghz, \$1550) :

model name	: Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms