Skip to content

Latest commit

 

History

History
909 lines (650 loc) · 22.8 KB

File metadata and controls

909 lines (650 loc) · 22.8 KB

🔝 Retour au Sommaire

18.4.2 Profiling Linux (gprof, Valgrind)

Introduction au Profiling sous Linux

Le profiling sous Linux dispose d'un écosystème d'outils puissants et gratuits qui permettent d'analyser en profondeur les performances de vos applications FreePascal. Contrairement à Windows, Linux offre nativement plusieurs outils complémentaires.

Pourquoi profiler sous Linux ?

  • Outils gratuits et open source : gprof, Valgrind, perf sont libres et très performants
  • Intégration système : accès direct aux métriques du noyau Linux
  • Précision : mesures très détaillées des performances CPU et mémoire
  • Débogage avancé : détection de fuites mémoire, erreurs d'accès, problèmes de cache

Vue d'Ensemble des Outils

Linux propose principalement deux outils complémentaires :

Outil Usage Principal Ce qu'il mesure
gprof Profiling CPU Temps d'exécution des fonctions
Valgrind Profiling mémoire et CPU Utilisation mémoire, fuites, accès invalides, cache

Ces outils sont complémentaires : gprof pour le temps CPU, Valgrind pour la mémoire et les problèmes subtils.


Partie 1 : gprof - Profiling Temps CPU

Qu'est-ce que gprof ?

gprof (GNU Profiler) est l'outil standard GNU pour le profiling de temps d'exécution. Il fonctionne exactement de la même manière sous Linux que sous Windows, mais avec une meilleure intégration système.

Installation de gprof

Sur Ubuntu/Debian :

sudo apt update  
sudo apt install binutils gcc gdb

Sur Fedora/RedHat :

sudo dnf install binutils gcc gdb

gprof est généralement déjà installé avec le compilateur GCC.

Configuration du Projet

Compilation avec Options de Profiling

Pour utiliser gprof, compilez votre programme avec l'option -pg :

fpc -pg -gl MonProgramme.pas

Options importantes :

  • -pg : Active l'instrumentation pour gprof
  • -gl : Inclut les informations de débogage avec numéros de ligne
  • -O2 : (Optionnel) Active les optimisations pour profiler du code réaliste

Exemple Complet

# Compilation
fpc -pg -gl -O2 MonProgramme.pas

# Vérification
ls -lh MonProgramme
# Le fichier doit être légèrement plus gros (code d'instrumentation)

Utilisation de gprof

Étape 1 : Exécuter le Programme

./MonProgramme

Le programme génère automatiquement un fichier gmon.out dans le répertoire courant.

Note : Le fichier gmon.out est écrasé à chaque exécution.

Étape 2 : Générer le Rapport

gprof MonProgramme gmon.out > rapport.txt

Ou pour visualiser directement :

gprof MonProgramme gmon.out | less

Étape 3 : Analyser les Résultats

Ouvrez rapport.txt avec votre éditeur préféré :

nano rapport.txt
# ou
gedit rapport.txt
# ou
kate rapport.txt

Structure du Rapport gprof

Le rapport contient deux sections principales :

1. Flat Profile (Profil Plat)

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 33.34      0.35     0.35   500000     0.00     0.00  CalculerDistance
 28.57      0.65     0.30   100000     0.00     0.01  TrierDonnees
 19.05      0.85     0.20  1000000     0.00     0.00  ValiderEntree
 14.29      1.00     0.15        1   150.00   1000.00  FonctionPrincipale

Lecture du tableau :

  • % time : Pourcentage du temps total dans cette fonction (33.34% pour CalculerDistance)
  • cumulative seconds : Temps cumulé (0.35s pour la première fonction)
  • self seconds : Temps passé uniquement dans cette fonction
  • calls : Nombre d'appels (500 000 fois pour CalculerDistance)
  • self ms/call : Temps moyen par appel
  • name : Nom de la fonction

Interprétation : CalculerDistance est la fonction la plus coûteuse (33% du temps) et est appelée très souvent (500 000 fois). C'est la priorité pour l'optimisation.

2. Call Graph (Graphe d'Appels)

index % time    self  children    called     name
                                                 <spontaneous>
[1]    100.0    0.15     0.85                 FonctionPrincipale [1]
                0.35     0.00  500000/500000     CalculerDistance [2]
                0.30     0.00  100000/100000     TrierDonnees [3]
                0.20     0.00 1000000/1000000    ValiderEntree [4]
-----------------------------------------------
                0.35     0.00  500000/500000     FonctionPrincipale [1]
[2]     33.3    0.35     0.00  500000         CalculerDistance [2]
-----------------------------------------------

Ce que ça montre :

  • FonctionPrincipale [1] est le point d'entrée (100% du temps)
  • Elle appelle CalculerDistance 500 000 fois
  • Chaque fonction est liée à ses appelants et appelés

Options Avancées de gprof

Afficher seulement les N fonctions les plus coûteuses

gprof -b MonProgramme gmon.out | head -n 50 > top50.txt

Exclure certaines fonctions

gprof -e FonctionAIgnorer MonProgramme gmon.out

Format de sortie annoté

gprof -A MonProgramme gmon.out > rapport_annote.txt

Conseils pour gprof sous Linux

1. Désactiver ASLR pour des Résultats Cohérents

ASLR (Address Space Layout Randomization) peut perturber le profiling :

# Désactiver temporairement
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

# Exécuter le profiling
./MonProgramme
gprof MonProgramme gmon.out > rapport.txt

# Réactiver (important pour la sécurité)
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

2. Profiler sur un Seul CPU

Pour des résultats plus stables :

taskset -c 0 ./MonProgramme

Cela force l'exécution sur le CPU 0 uniquement.

3. Script de Profiling Automatisé

Créez un script profile.sh :

#!/bin/bash
# Script de profiling automatique

PROGRAM=$1

if [ -z "$PROGRAM" ]; then
    echo "Usage: ./profile.sh MonProgramme"
    exit 1
fi

# Compilation
echo "Compilation avec profiling..."  
fpc -pg -gl -O2 $PROGRAM.pas

# Exécution
echo "Exécution du programme..."
./$PROGRAM

# Génération du rapport
echo "Génération du rapport..."  
gprof $PROGRAM gmon.out > profiling_$(date +%Y%m%d_%H%M%S).txt

echo "Rapport généré : profiling_$(date +%Y%m%d_%H%M%S).txt"

Utilisation :

chmod +x profile.sh
./profile.sh MonProgramme

Partie 2 : Valgrind - Profiling Mémoire et Performance

Qu'est-ce que Valgrind ?

Valgrind est une suite d'outils de débogage et de profiling extrêmement puissante. Elle fonctionne en exécutant votre programme dans une machine virtuelle qui surveille chaque accès mémoire et instruction CPU.

Outils de Valgrind

Valgrind contient plusieurs outils :

Outil Fonction
Memcheck Détection de fuites mémoire et erreurs d'accès
Cachegrind Profiling du cache CPU (L1, L2, L3)
Callgrind Profiling détaillé des appels de fonction
Massif Profiling de l'utilisation du heap (tas)
Helgrind Détection de problèmes de threads

Installation de Valgrind

# Ubuntu/Debian
sudo apt update  
sudo apt install valgrind

# Fedora/RedHat
sudo dnf install valgrind

# Vérification
valgrind --version

Outil 1 : Memcheck - Détection de Problèmes Mémoire

Qu'est-ce que Memcheck détecte ?

  • Fuites mémoire : mémoire allouée mais jamais libérée
  • Accès invalides : lecture/écriture hors des limites
  • Utilisation de mémoire non initialisée
  • Double libération : libérer deux fois la même mémoire
  • Débordements de pile (stack overflow)

Compilation pour Memcheck

Pas besoin de -pg, mais ajoutez des informations de débogage :

fpc -gl -gh MonProgramme.pas

Options :

  • -gl : Informations de débogage avec numéros de ligne
  • -gh : Active le heap manager pour tracer les allocations

Utilisation de Memcheck

Commande de base :

valgrind --leak-check=full ./MonProgramme

Commande détaillée avec toutes les options utiles :

valgrind \
  --leak-check=full \
  --show-leak-kinds=all \
  --track-origins=yes \
  --verbose \
  --log-file=memcheck_rapport.txt \
  ./MonProgramme

Options expliquées :

  • --leak-check=full : Recherche exhaustive des fuites
  • --show-leak-kinds=all : Affiche tous les types de fuites
  • --track-origins=yes : Trace l'origine des valeurs non initialisées
  • --verbose : Mode verbeux pour plus de détails
  • --log-file=... : Sauvegarde le rapport dans un fichier

Lecture du Rapport Memcheck

Exemple de sortie :

==12345== Memcheck, a memory error detector
==12345== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==12345== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==12345== Command: ./MonProgramme
==12345==

==12345== Invalid write of size 4
==12345==    at 0x401234: EcrireDansTableau (MonProgramme.pas:42)
==12345==    by 0x401156: FonctionPrincipale (MonProgramme.pas:28)
==12345==    by 0x401089: main (MonProgramme.pas:15)
==12345==  Address 0x5204050 is 0 bytes after a block of size 40 alloc'd
==12345==    at 0x4C2DB8F: malloc (vg_replace_malloc.c:299)
==12345==    by 0x401123: AllouerTableau (MonProgramme.pas:25)

==12345== LEAK SUMMARY:
==12345==    definitely lost: 120 bytes in 3 blocks
==12345==    indirectly lost: 0 bytes in 0 blocks
==12345==      possibly lost: 40 bytes in 1 blocks
==12345==    still reachable: 0 bytes in 0 blocks
==12345==         suppressed: 0 bytes in 0 blocks

Interprétation :

  1. Invalid write : Écriture en dehors des limites à la ligne 42
  2. definitely lost: 120 bytes : Fuite mémoire confirmée (3 allocations jamais libérées)
  3. possibly lost: 40 bytes : Fuite possible (à vérifier)
  4. Les numéros de ligne permettent de localiser exactement le problème

Types de Fuites Mémoire

definitely lost     → Fuite confirmée, plus aucune référence  
indirectly lost     → Fuite indirecte (contenue dans une fuite principale)  
possibly lost       → Peut-être une fuite (pointeur vers le milieu du bloc)  
still reachable     → Mémoire non libérée mais toujours accessible (pas grave)  
suppressed          → Fuites ignorées (bibliothèques système)

Outil 2 : Cachegrind - Profiling du Cache

Qu'est-ce que Cachegrind analyse ?

Cachegrind simule le fonctionnement des caches CPU (L1, L2, L3) et compte :

  • Les instructions exécutées
  • Les accès mémoire
  • Les défauts de cache (cache miss) qui ralentissent l'exécution

Utilisation de Cachegrind

valgrind --tool=cachegrind ./MonProgramme

Cela génère un fichier cachegrind.out.<pid>.

Visualiser les Résultats

Utilisez cg_annotate pour lire le rapport :

cg_annotate cachegrind.out.12345

Exemple de sortie :

--------------------------------------------------------------------------------
Ir                    I1mr  ILmr          Dr          D1mr       DLmr          Dw         D1mw  DLmw file:function
--------------------------------------------------------------------------------
1,234,567,890          456    123  345,678,901     12,345      2,345  123,456,789       3,456   890  MonProgramme.pas:CalculerDistance
  987,654,321          234     89  234,567,890     10,234      1,890  100,000,000       2,100   567  MonProgramme.pas:TrierDonnees

Colonnes expliquées :

  • Ir : Instructions Read (nombre d'instructions exécutées)
  • I1mr : Instruction cache L1 miss rate (défauts L1)
  • Dr : Data Read (lectures de données)
  • D1mr : Data cache L1 miss rate (défauts lecture L1)
  • Dw : Data Write (écritures de données)
  • D1mw : Data cache L1 write miss (défauts écriture L1)

Plus le nombre de "miss" est élevé, plus le code est lent car les données ne sont pas dans le cache rapide.

Annoter le Code Source

Pour voir ligne par ligne les statistiques :

cg_annotate --auto=yes cachegrind.out.12345 MonProgramme.pas > rapport_annote.txt

Outil 3 : Callgrind - Profiling d'Appels Détaillé

Avantages de Callgrind vs gprof

  • Plus précis : compte exact des instructions
  • Pas besoin de -pg : pas d'instrumentation du code
  • Visualisation graphique : avec KCachegrind

Utilisation de Callgrind

valgrind --tool=callgrind ./MonProgramme

Génère un fichier callgrind.out.<pid>.

Analyser avec KCachegrind (Interface Graphique)

Installation :

sudo apt install kcachegrind

Utilisation :

kcachegrind callgrind.out.12345

KCachegrind affiche :

  • Un graphique visuel des appels de fonction
  • Le temps passé dans chaque fonction (avec couleurs)
  • Le graphe d'appels interactif
  • Les statistiques de cache
  • Le code source annoté

C'est l'outil le plus visuel et intuitif pour le profiling !

Callgrind en Ligne de Commande

callgrind_annotate callgrind.out.12345

Affiche un rapport similaire à gprof mais plus détaillé.

Outil 4 : Massif - Profiling du Heap

Qu'est-ce que Massif mesure ?

Massif trace l'utilisation du heap (tas) dans le temps :

  • Combien de mémoire est allouée
  • Quand les allocations ont lieu
  • Quelles fonctions allouent le plus de mémoire

Utilisation de Massif

valgrind --tool=massif ./MonProgramme

Génère un fichier massif.out.<pid>.

Visualiser avec ms_print

ms_print massif.out.12345

Exemple de sortie (graphique ASCII) :

    MB
120 |                                                       :#
    |                                                    @@@:#
    |                                                 @@@@@@:#
100 |                                              @@@@@@@@@:#
    |                                           @@@@@@@@@@@@:#
    |                                        @@@@@@@@@@@@@@@:#
 80 |                                     @@@@@@@@@@@@@@@@@@:#
    |                                  @@@@@@@@@@@@@@@@@@@@@:#
    |                               @@@@@@@@@@@@@@@@@@@@@@@@:#
 60 |                            @@@@@@@@@@@@@@@@@@@@@@@@@@@:#
    |                         @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
    |                      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
 40 |                   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
    |                @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
    |             @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
 20 |          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
    |       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
    |    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:#
  0 +----------------------------------------------------------------------->s
    0                                                                   10.00

Chaque @ ou : représente une allocation. On voit clairement :

  • Le pic de mémoire à 120 MB
  • La croissance progressive de l'utilisation
  • Les moments d'allocation intense

Interface Graphique avec Massif-Visualizer

Installation :

sudo apt install massif-visualizer

Utilisation :

massif-visualizer massif.out.12345

Affiche un graphique moderne avec :

  • Courbe de l'utilisation mémoire dans le temps
  • Liste des fonctions qui allouent le plus
  • Détails de chaque snapshot

Comparaison gprof vs Valgrind

Critère gprof Valgrind
Vitesse Rapide (10-20% overhead) Lent (10-50x plus lent)
Précision Sampling (échantillonnage) Exacte (instruction par instruction)
Mémoire Non Oui (Memcheck, Massif)
Cache Non Oui (Cachegrind)
Setup Compilation avec -pg Aucune compilation spéciale
Threads Limité Excellent (Helgrind)
Usage Profiling rapide temps CPU Analyse approfondie et débogage

Règle générale :

  • gprof pour identifier rapidement les fonctions lentes
  • Valgrind pour comprendre en détail les problèmes de mémoire et performance

Workflows Pratiques

Workflow 1 : Optimisation de Performance

# 1. Profiling rapide avec gprof
fpc -pg -O2 MonProgramme.pas
./MonProgramme
gprof MonProgramme gmon.out > gprof_rapport.txt

# 2. Identifier les fonctions lentes dans gprof_rapport.txt

# 3. Analyse détaillée avec Callgrind
fpc -gl -O2 MonProgramme.pas  
valgrind --tool=callgrind ./MonProgramme  
kcachegrind callgrind.out.*

# 4. Optimiser le code

# 5. Recommencer pour vérifier l'amélioration

Workflow 2 : Chasse aux Fuites Mémoire

# 1. Compilation avec débogage
fpc -gl -gh MonProgramme.pas

# 2. Détection de fuites avec Memcheck
valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --log-file=memcheck.txt \
         ./MonProgramme

# 3. Analyser memcheck.txt pour trouver les fuites

# 4. Corriger le code

# 5. Profiler l'utilisation heap avec Massif
valgrind --tool=massif ./MonProgramme  
massif-visualizer massif.out.*

# 6. Vérifier que l'utilisation mémoire est normale

Workflow 3 : Optimisation Cache

# 1. Profiler le cache
valgrind --tool=cachegrind ./MonProgramme

# 2. Analyser les défauts de cache
cg_annotate --auto=yes cachegrind.out.* MonProgramme.pas

# 3. Identifier les structures de données avec beaucoup de cache miss

# 4. Réorganiser les données pour améliorer la localité
#    (ex: structure of arrays au lieu de array of structures)

# 5. Re-profiler pour confirmer l'amélioration

Conseils et Astuces Avancés

1. Profiler Uniquement une Partie du Code

Avec Callgrind, vous pouvez activer/désactiver le profiling dynamiquement :

valgrind --tool=callgrind --instr-atstart=no ./MonProgramme

Dans votre code Pascal, utilisez les fonctions de contrôle :

{$IFDEF UNIX}
uses
  BaseUnix;

procedure DemarrerProfiling;  
begin
  // Démarre l'instrumentation Callgrind
  FpSystem('callgrind_control -i on');
end;

procedure ArreterProfiling;  
begin
  // Arrête l'instrumentation
  FpSystem('callgrind_control -i off');
end;
{$ENDIF}

// Utilisation
DemarrerProfiling;
  CodeACritiqueAProfler;
ArreterProfiling;

2. Comparer Deux Profilages

Utilisez cg_diff pour comparer deux sessions Callgrind :

# Profiling avant optimisation
valgrind --tool=callgrind -o callgrind.avant ./MonProgramme

# Optimisation du code...

# Profiling après optimisation
valgrind --tool=callgrind -o callgrind.apres ./MonProgramme

# Comparaison
cg_diff callgrind.avant callgrind.apres

3. Profiling Multi-Thread

Pour profiler les applications multi-threadées :

# Vérifier les race conditions
valgrind --tool=helgrind ./MonProgramme

# Profiler avec support threads
valgrind --tool=callgrind --separate-threads=yes ./MonProgramme

4. Suppressions Valgrind

Créez un fichier suppressions.txt pour ignorer les fausses alertes :

{
   <nom_suppression>
   Memcheck:Leak
   fun:malloc
   fun:InitializeRTL
}

Utilisez-le :

valgrind --suppressions=suppressions.txt ./MonProgramme

5. Script de Profiling Complet

Créez profile_complet.sh :

#!/bin/bash
PROGRAM=$1

echo "=== Compilation ==="  
fpc -gl -gh -O2 $PROGRAM.pas

echo "=== Memcheck - Fuites mémoire ==="  
valgrind --leak-check=full \
         --log-file=memcheck.log \
         ./$PROGRAM

echo "=== Callgrind - Performance ==="  
valgrind --tool=callgrind \
         --callgrind-out-file=callgrind.out \
         ./$PROGRAM

echo "=== Massif - Heap ==="  
valgrind --tool=massif \
         --massif-out-file=massif.out \
         ./$PROGRAM

echo "=== Génération des rapports ==="  
callgrind_annotate callgrind.out > callgrind_rapport.txt  
ms_print massif.out > massif_rapport.txt

echo "=== Terminé ==="  
echo "Rapports disponibles:"  
echo "  - memcheck.log"  
echo "  - callgrind_rapport.txt"  
echo "  - massif_rapport.txt"

Exemple Complet Commenté

Code avec Problèmes

program ExempleProfilageBuggy;

{$mode objfpc}{$H+}

uses
  SysUtils, Classes;

type
  TPersonne = class
    Nom: string;
    Age: Integer;
  end;

var
  Liste: TList;
  i: Integer;
  p: TPersonne;

begin
  Liste := TList.Create;

  // PROBLÈME 1: Fuite mémoire - objets jamais libérés
  for i := 1 to 10000 do
  begin
    p := TPersonne.Create;
    p.Nom := 'Personne' + IntToStr(i);
    p.Age := Random(100);
    Liste.Add(p);
  end;

  // PROBLÈME 2: Accès hors limites
  WriteLn('Accès à l''élément 10001:', TPersonne(Liste[10001]).Nom);

  // PROBLÈME 3: Liste libérée mais pas son contenu
  Liste.Free;
end.

Profiling avec Valgrind

# Compilation
fpc -gl -gh ExempleProfilageBuggy.pas

# Détection des problèmes
valgrind --leak-check=full ./ExempleProfilageBuggy

Rapport Valgrind

==12345== Invalid read of size 8
==12345==    at 0x401234: main (ExempleProfilageBuggy.pas:28)
==12345==  Address 0x5204050 is not stack'd, malloc'd or (recently) free'd

==12345== LEAK SUMMARY:
==12345==    definitely lost: 480,000 bytes in 10,000 blocks

Diagnostic :

  1. Accès invalide ligne 28 (hors limites)
  2. Fuite de 480 Ko (10 000 objets TPersonne jamais libérés)

Code Corrigé

program ExempleProfilageCorrige;

{$mode objfpc}{$H+}

uses
  SysUtils, Classes;

type
  TPersonne = class
    Nom: string;
    Age: Integer;
  end;

var
  Liste: TList;
  i: Integer;
  p: TPersonne;

begin
  Liste := TList.Create;
  try
    // Création des objets
    for i := 1 to 10000 do
    begin
      p := TPersonne.Create;
      p.Nom := 'Personne' + IntToStr(i);
      p.Age := Random(100);
      Liste.Add(p);
    end;

    // Accès VALIDE (dans les limites)
    if Liste.Count > 0 then
      WriteLn('Première personne: ', TPersonne(Liste[0]).Nom);

  finally
    // Libération correcte de tous les objets
    for i := 0 to Liste.Count - 1 do
      TPersonne(Liste[i]).Free;
    Liste.Free;
  end;
end.

Vérification

valgrind --leak-check=full ./ExempleProfilageCorrige

Résultat :

==12346== HEAP SUMMARY:
==12346==     in use at exit: 0 bytes in 0 blocks
==12346==   total heap usage: 10,001 allocs, 10,001 frees
==12346==
==12346== All heap blocks were freed -- no leaks are possible

✅ Aucune fuite, tous les objets libérés correctement !


Conclusion

Le profiling sous Linux offre des outils exceptionnels :

gprof

✅ Rapide et simple pour identifier les fonctions lentes
✅ Intégré nativement
✅ Parfait pour une première analyse

Valgrind

✅ Détection exhaustive des problèmes mémoire
✅ Profiling très précis du CPU et du cache
✅ Visualisation graphique avec KCachegrind
✅ Indispensable pour du code robuste

Recommandation : Utilisez les deux ! gprof pour la vitesse, Valgrind pour la qualité et la rigueur.

Règle d'or du profiling :

Mesurez, optimisez, mesurez à nouveau. Jamais dans l'autre sens !

⏭️ Analyse statique du code