Analyse et optimisation en C++ et Python

publicité
Analyse et optimisation en C++ et Python
Denis Steckelmacher
[email protected]
Université Libre de Bruxelles
12 décembre 2016
Débogage en C++
Analyse des performances
Optimisation
Introduction
1. Débogage en C++
2. Profilage en C++ et Python
3. Optimisation en C++ et Python
2/47
Débogage en C++
Analyse des performances
Optimisation
Table des matires
Débogage en C++
Analyse des performances
Optimisation
3/47
Débogage en C++
Analyse des performances
Optimisation
Crash en C++
$ ./mon_programme
Segmentation fault
Pas beaucoup d’information !
4/47
Débogage en C++
Analyse des performances
Optimisation
GDB
I
Surveille un programme et détecte les crashs
I
Affiche la pile d’appel
I
Permet l’exécution pas-à-pas
5/47
Débogage en C++
Analyse des performances
Optimisation
GDB
$ gdb --args ./mon_programme
[...]
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x00000000004007f1 in do_crash(char**) ()
(gdb)
Toujours pas beaucoup d’information !
6/47
Débogage en C++
Analyse des performances
Optimisation
Options de compilation
-g Ajoute des informations de débogage
-O2 Optimise le code généré
-O3 Optimise encore plus
-Ofast Optimise au point de rendre des choses incorrectes
-flto Optimise au moment du link
7/47
Débogage en C++
Analyse des performances
Optimisation
GDB
$ gdb --args ./mon_programme
[...]
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x00000000004007f1 in do_crash (argv=0x7fffffffdb88)
at mon_programme.cpp:5
5 std::cout << argv[1][1] << std::endl;
(gdb)
Numéro de ligne auquel le crash survient
8/47
Débogage en C++
Analyse des performances
Optimisation
GDB
(gdb) bt
#0
#1
0x00000000004007f1 in
at
0x000000000040082e in
at
do_crash (argv=0x7fffffffdb88)
mon_programme.cpp:5
main (argc=1, argv=0x7fffffffdb88
mon_programme.cpp:10
Pile d’appel
9/47
Débogage en C++
Analyse des performances
Optimisation
GDB
(gdb) print argv[1]
$2 = 0x0
Inspection de variables
10/47
Débogage en C++
Analyse des performances
Optimisation
Et s’il est déjà trop tard ?
$ ulimit -c unlimited
$ ./mon_programme
Segmentation fault (core dumped)
$ gdb mon_programme core
[...]
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004007f1 in do_crash (argv=0x7ffeb27c3a28)
at mon_programme.cpp:5
5 std::cout << argv[1][1] << std::endl;
Analyse post-mortem
11/47
Débogage en C++
Analyse des performances
Optimisation
Table des matires
Débogage en C++
Analyse des performances
Optimisation
12/47
Débogage en C++
Analyse des performances
Optimisation
Performances en C++/Python
$ perf record ./mon_programme
$ perf annotate
$ sudo perf top
13/47
Débogage en C++
Analyse des performances
Optimisation
Performances en C++/Python
14/47
Débogage en C++
Analyse des performances
Optimisation
Analyse détaillée de code Python
$ sudo pip3 install line_profiler
$ kernprof -l mon_programme.py
$ python3 -m line_profiler mon_programme.py.lprof
15/47
Débogage en C++
Analyse des performances
Optimisation
Analyse détaillée de code Python
% Time Line Contents
=====================
@profile
def main():
0.0
N = 0
0.0
0.0
0.5
49.0
50.5
0.0
for i in range(1000):
N += 80
for j in range(1000):
for k in range(100):
N += 2
print(N)
16/47
Débogage en C++
Analyse des performances
Optimisation
Table des matires
Débogage en C++
Analyse des performances
Optimisation
17/47
Débogage en C++
Analyse des performances
Optimisation
Pourquoi optimiser ?
I
Un programme lent est désagréable voire impossible à utiliser
I
Un programme pas trop lent mais inefficace consommera trop
d’énergie
I
Le temps, c’est de l’argent, l’énergie aussi
Performances et prix des derniers processeurs Intel :
Processeur
Core i7-6950X
Core i7-6850K
Core i7-6800K
Performances
19 978
14 502
13 596
Prix
1567 $
585 $
392 $
18/47
Débogage en C++
Analyse des performances
Optimisation
Optimisations automatiques
I
Les compilateurs modernes (GCC, Clang, ICC, Visual Studio)
sont capables d’optimiser votre code eux-mêmes
I
Laissez-les faire ! Ne compliquez pas votre code inutilement si
le compilateur sait faire la même chose
I
Écrivez du code clair en sachant que votre compilateur
l’optimisera pour vous
Avec GCC, Clang et ICC, les optimisations automatiques sont
activées dès que l’option -O1, -O2 ou -O3 est présente sur la ligne
de commande. Sous Visual Studio, il faut activer le mode Release.
19/47
Débogage en C++
Analyse des performances
Optimisation
Factorisation
Ne calcule une valeur qu’une seule fois, même si elle est utilisée
plusieurs fois.
1
2
players [ i + 1]. isA = rand () & 1;
players [ i + 1]. isTolerant = rand () & 1;
Devient :
1
Player * tmp = & players [ i + 1];
2
3
4
tmp - > isA = rand () & 1;
tmp - > isTolerant = rand () & 1;
20/47
Débogage en C++
Analyse des performances
Optimisation
Propagation des constantes
Si un calcul est fait sur des données connues à la compilation, le
compilateur fait les calculs
1
2
i n t a = 2 + 3 * 5;
f l o a t f = exp (2);
Devient :
1
2
i n t a = 17;
f l o a t f = 7.3890561;
21/47
Débogage en C++
Analyse des performances
Optimisation
Réordonnancement
Parfois, faire une opération avant une autre la rend plus rapide.
1
2
c = a + b;
d = exp ( e );
Devient :
1
2
d = exp ( e );
c = a + b;
22/47
Débogage en C++
Analyse des performances
Optimisation
Inlining
Recopie une fonction au lieu de l’appeler. Cette optimisation est
l’une des plus importante et des plus efficaces.
1
2
3
i n t add ( i n t a , i n t b ) {
r e t u r n a + b;
}
4
5
i n t s = add (3 , 5)
Devient :
1
i n t s = 8;
// Constantes propag é es
23/47
Débogage en C++
Analyse des performances
Optimisation
Suppression du code mort
Du code jamais appelé peut être supprimé. Généralement,
l’inlining produit beaucoup de code mort.
1
2
3
4
i n t add ( i n t a , b o o l negate ) {
i f ( negate ) { r e t u r n -a ; }
e l s e { r e t u r n a; }
}
5
6
i n t t = negate ( var , t r u e );
Devient :
1
i n t t = - var ;
24/47
Débogage en C++
Analyse des performances
Optimisation
Factorisation des invariants de boucle
Ce qui ne dépend pas d’une variable qui change dans une boucle
peut en être sorti.
1
2
3
f o r ( i n t i =0; i < N ; ++ i ) {
tab [ i ] = exp ( a );
}
Devient :
1
f l o a t tmp = exp ( a );
2
3
4
5
f o r ( i n t i =0; i < N ; ++ i ) {
tab [ i ] = tmp ;
}
25/47
Débogage en C++
Analyse des performances
Optimisation
Duplication de boucles
Si une boucle contient un gros if, elle sera remplacée par un gros if
qui contient des boucles
1
2
3
4
5
6
7
f o r ( i n t i =0; i < N ; ++ i ) {
i f ( a == b ) {
tab [ i ] += 1;
} else {
tab [ i ] -= 1;
}
}
26/47
Débogage en C++
Analyse des performances
Optimisation
Duplication de boucles
Si une boucle contient un gros if, elle sera remplacée par un gros if
qui contient des boucles
1
2
3
4
5
6
7
8
9
i f ( a == b ) {
f o r ( i n t i =0;
tab [ i ] +=
}
} else {
f o r ( i n t i =0;
tab [ i ] -=
}
}
i < N ; ++ i ) {
1;
i < N ; ++ i ) {
1;
27/47
Débogage en C++
Analyse des performances
Optimisation
Échange de boucles
Deux boucles peuvent être échangées pour optimiser les calculs
1
2
3
4
5
f o r ( i n t x =0; x < N ; ++ x ) {
f o r ( i n t y =0; y < N ; ++ y ) {
tab [ y * N + x ] += 1;
}
}
28/47
Débogage en C++
Analyse des performances
Optimisation
Échange de boucles
Deux boucles peuvent être échangées pour optimiser les calculs
1
2
f o r ( i n t y =0; y < N ; ++ y ) {
i n t tmp = y * N ;
3
f o r ( i n t x =0; x < N ; ++ x ) {
tab [ tmp + x ] += 1;
}
4
5
6
7
}
29/47
Débogage en C++
Analyse des performances
Optimisation
Déroulage de boucles
Les boucles dont le nombre de tours est connu peuvent être
déroulées
1
2
3
f o r ( i n t i =0; i <30; ++ i ) {
tab [ i ] *= 2;
}
Devient :
1
2
3
4
5
f o r ( i n t i =0; i <28; i += 3) {
tab [ i ] *= 2;
tab [ i +1] *= 2;
tab [ i +2] *= 2;
}
30/47
Débogage en C++
Analyse des performances
Optimisation
Déroulage de boucles
De même quand le nombre de tours est inconnu (mais le code
généré est alors plus compliqué)
1
2
3
f o r ( i n t i =0; i < N ; ++ i ) {
tab [ i ] *= 2;
}
31/47
Débogage en C++
Analyse des performances
Optimisation
Déroulage de boucles
De même quand le nombre de tours est inconnu (mais le code
généré est alors plus compliqué)
1
2
3
4
5
f o r ( i n t i =0; i <N -2; i += 3) {
tab [ i ] *= 2;
tab [ i +1] *= 2;
tab [ i +2] *= 2;
}
6
7
8
9
f o r ( i n t i = N /3*3; i < N ; ++ i ) {
tab [ i ] *= 2;
}
32/47
Débogage en C++
Analyse des performances
Optimisation
Link-Time Optimization
L’optimisation se fait au moment de produire chaque fichier .o,
qui sont ensuite fusionnés
Optimisation
g++
g++
ld
g++
.cpp
.o
33/47
Débogage en C++
Analyse des performances
Optimisation
Link-Time Optimization
Le compilateur n’optimise plus rien, mais laisse le lieur faire le
travail. Il a alors connaissance de tous les fichiers .o et l’inlining
devient bien plus performant (30% de gains sur Firefox)
g++
Optimisation
g++
ld
g++
.cpp
.o
34/47
Débogage en C++
Analyse des performances
Optimisation
Link-Time Optimization
I
Toutes les optimisations présentées ici sont activées par -O2
I
Sauf LTO, qui s’active à l’aide de -flto
35/47
Débogage en C++
Analyse des performances
Optimisation
Optimisations manuelles
Les optimisations les plus difficiles mais également les plus
intéressantes se font manuellement :
I
Meilleurs algorithmes
I
Placement des objets en mémoire
I
Contrôle de flux
36/47
Débogage en C++
Analyse des performances
Optimisation
Accès mémoire direct
tab
tab[i] += 1;
tmp ← tab[i]
tmp ← tmp + 1
tab[i] ← tmp
tab[i]
La mémoire centrale est beaucoup plus lente que le processeur !
37/47
Débogage en C++
Analyse des performances
Optimisation
Hiérarchie des caches
32 KB
tab
tab[i] += 1;
tmp ← tab[i]
tmp ← tmp + 1
tab[i] ← tmp
tab
L1
tab[i]
5 cycles
6 GB
512 KB
tab[i]
...
tab[i+15]
tab
L2
12 cycles
tab[i]
...
tab[i+15]
~50 ns
Les caches réduisent la latence à condition de respecter certaines
conditions
38/47
Débogage en C++
Analyse des performances
Optimisation
Accès fragmentés
Temps d’accès ralenti et des données inutiles sont lues
tab[2]
tab[18]
tab[2]
tab[18]
39/47
Débogage en C++
Analyse des performances
Optimisation
Accès non-alignés
Entre 2 × S et 64 octets doivent être lus depuis la mémoire
tab2[15]
tab2[15]
40/47
Débogage en C++
Analyse des performances
Optimisation
Registres
I
Temps d’accès très rapide
I
Très peu nombreux (16 sur les derniers Intel et AMD 64-bit)
I
Le compilateur met les variables les plus utilisées dans les
registres, les autres en mémoire
I
Évitez d’utiliser plus de 16 variables à la fois !
41/47
Débogage en C++
Analyse des performances
Optimisation
Liste de structures (AOS)
Petits accès fragmentés
42/47
Débogage en C++
Analyse des performances
Optimisation
Structure de listes (SOA)
Un seul accès contigu
43/47
Débogage en C++
Analyse des performances
Optimisation
AOS vers SOA
1
2
players [ i ]. payoff = 0;
players [ i ]. judgesGood [ j ] = rand () & 3;
Le [i] change de place :
1
2
players . payoff [ i ] = 0;
players . judgesGood [ i ][ j ] = rand () & 3;
44/47
Débogage en C++
Analyse des performances
Optimisation
Conditions
I
La condition de chaque if doit être évaluée
I
Le processeur ne peut que difficilement prédire si un if sera
vrai ou faux
I
Les while et les for cachent des sauts conditionnels
45/47
Débogage en C++
Analyse des performances
Optimisation
Et Python ?
S’applique à Python :
I
Meilleurs algorithmes
I
Organisation de la mémoire
I
Utilisation de Numpy, Scipy, Theano, etc
I
PyPy !
I
Python est codé en C, plus le C est optimisé, plus Python va
vite
Le moins de code Python s’exécute, le mieux !
46/47
Téléchargement