🔝 Retour au Sommaire
Imaginez que vous avez créé une application Delphi qui fonctionne correctement, mais qui semble "lente" ou qui "rame" parfois. Comment savoir quelle partie du code est responsable de cette lenteur ? Comment améliorer les performances sans tout réécrire ? C'est exactement à ces questions que répond le profilage.
Le profilage est le processus qui consiste à mesurer et analyser les performances de votre application pour identifier les parties du code qui consomment le plus de temps ou de ressources. Une fois ces "goulots d'étranglement" identifiés, vous pouvez les optimiser de manière ciblée et efficace.
Pour un débutant, il est important de comprendre que l'optimisation prématurée est souvent contre-productive. La règle d'or est : "Faites d'abord fonctionner votre code correctement, puis mesurez avant d'optimiser."
En tant que développeur, vous pourriez penser savoir quelle partie de votre code est lente. Mais l'expérience montre que les développeurs se trompent souvent sur les vraies causes de lenteur. Le profilage vous donne des données objectives plutôt que des suppositions.
Il est inutile de passer des heures à optimiser une fonction qui ne représente que 0,1% du temps d'exécution total de votre programme. Le profilage vous aide à concentrer vos efforts là où ils auront le plus d'impact.
Une fois votre application optimisée, le profilage régulier vous permet de détecter rapidement si de nouvelles modifications ont introduit des problèmes de performance.
Le profilage vous aide à mieux comprendre comment votre code s'exécute réellement, ce qui améliore vos compétences de développeur.
Avant de plonger dans le profilage, il est important de comprendre quelques concepts fondamentaux.
C'est le temps que prend une fonction ou une portion de code pour s'exécuter. On le mesure généralement en millisecondes (ms) ou en microsecondes (μs).
Le nombre de fois qu'une fonction est appelée pendant l'exécution du programme. Une fonction rapide appelée un million de fois peut causer plus de problèmes qu'une fonction lente appelée une seule fois.
- Temps inclusif : Le temps total passé dans une fonction, incluant le temps des fonctions qu'elle appelle
- Temps exclusif : Le temps passé uniquement dans le code de la fonction elle-même, sans compter les appels à d'autres fonctions
Exemple :
FonctionA (100ms au total)
├── Son propre code (20ms)
├── Appelle FonctionB (50ms)
└── Appelle FonctionC (30ms)
Temps inclusif de FonctionA : 100ms
Temps exclusif de FonctionA : 20ms
Dans la plupart des applications, environ 80% du temps d'exécution est passé dans environ 20% du code. Le profilage vous aide à identifier ces 20% critiques.
La façon dont le temps d'exécution d'un algorithme évolue avec la taille des données :
- O(1) : Temps constant (accès à un élément d'un tableau)
- O(n) : Temps linéaire (parcourir une liste)
- O(n²) : Temps quadratique (boucles imbriquées)
- O(log n) : Temps logarithmique (recherche dichotomique)
Comprendre ces concepts vous aide à choisir les bonnes structures de données et algorithmes.
Avant d'utiliser des outils complexes, vous pouvez effectuer des mesures basiques avec du code simple.
Delphi fournit la classe TStopwatch qui permet de mesurer précisément le temps d'exécution.
uses
System.Diagnostics;
procedure MesureTempsExecution;
var
Chrono: TStopwatch;
TempsEcoule: Int64;
begin
// Démarrer le chronomètre
Chrono := TStopwatch.StartNew;
// Code à mesurer
EffectuerTraitementComplexe;
// Arrêter et obtenir le temps
Chrono.Stop;
TempsEcoule := Chrono.ElapsedMilliseconds;
ShowMessage(Format('Temps d''exécution : %d ms', [TempsEcoule]));
end;Avantages :
- Très simple à utiliser
- Précis pour des mesures de base
- Ne nécessite aucun outil externe
Limitations :
- Mesure manuelle, nécessite de modifier le code
- Donne uniquement le temps total, pas de détails sur les sous-fonctions
- Les appels répétés nécessitent du code supplémentaire
procedure AnalyserPerformances;
var
Chrono: TStopwatch;
TempsPartie1, TempsPartie2, TempsPartie3: Int64;
begin
// Mesurer la partie 1
Chrono := TStopwatch.StartNew;
TraitementPartie1;
TempsPartie1 := Chrono.ElapsedMilliseconds;
// Mesurer la partie 2
Chrono := TStopwatch.StartNew;
TraitementPartie2;
TempsPartie2 := Chrono.ElapsedMilliseconds;
// Mesurer la partie 3
Chrono := TStopwatch.StartNew;
TraitementPartie3;
TempsPartie3 := Chrono.ElapsedMilliseconds;
// Afficher les résultats
Memo1.Lines.Add(Format('Partie 1 : %d ms', [TempsPartie1]));
Memo1.Lines.Add(Format('Partie 2 : %d ms', [TempsPartie2]));
Memo1.Lines.Add(Format('Partie 3 : %d ms', [TempsPartie3]));
Memo1.Lines.Add(Format('Total : %d ms', [TempsPartie1 + TempsPartie2 + TempsPartie3]));
end;Pour des opérations très rapides, il faut les répéter plusieurs fois pour obtenir une mesure significative :
procedure MesurerOperationRapide;
var
Chrono: TStopwatch;
i: Integer;
NombreIterations: Integer;
TempsTotal: Int64;
TempsMoyen: Double;
begin
NombreIterations := 100000;
Chrono := TStopwatch.StartNew;
for i := 1 to NombreIterations do
begin
// Opération à mesurer
EffectuerCalculSimple(i);
end;
Chrono.Stop;
TempsTotal := Chrono.ElapsedMilliseconds;
TempsMoyen := TempsTotal / NombreIterations;
ShowMessage(Format('Temps moyen par opération : %.6f ms', [TempsMoyen]));
end;Utilisez GetProcessMemoryInfo (Windows) pour surveiller l'utilisation mémoire :
uses
Winapi.Windows, Winapi.PsAPI;
function ObtenirMemoireUtilisee: Cardinal;
var
MemCounters: TProcessMemoryCounters;
begin
MemCounters.cb := SizeOf(MemCounters);
if GetProcessMemoryInfo(GetCurrentProcess, @MemCounters, SizeOf(MemCounters)) then
Result := MemCounters.WorkingSetSize div 1024 // En Ko
else
Result := 0;
end;
procedure AnalyserMemoire;
var
MemoireAvant, MemoireApres: Cardinal;
begin
MemoireAvant := ObtenirMemoireUtilisee;
// Code à analyser
CreerBeaucoupObjets;
MemoireApres := ObtenirMemoireUtilisee;
ShowMessage(Format('Mémoire utilisée : %d Ko', [MemoireApres - MemoireAvant]));
end;Delphi inclut un profileur de base dans certaines éditions (Professional, Enterprise, Architect).
Comment l'utiliser :
- Ouvrez votre projet dans Delphi
- Allez dans Run > Run with Profiling (Exécuter avec profilage)
- Utilisez votre application normalement, en exécutant les fonctionnalités que vous souhaitez analyser
- Fermez l'application
- Delphi affiche automatiquement les résultats du profilage
Ce que vous verrez :
Le profileur affiche un rapport montrant :
- Les fonctions qui ont consommé le plus de temps
- Le nombre d'appels pour chaque fonction
- Le pourcentage du temps total pour chaque fonction
- Une arborescence des appels de fonctions
Limitations :
- Disponible uniquement dans certaines éditions de Delphi
- Profilage par échantillonnage (sampling), donc moins précis que le profilage instrumenté
- Interface parfois limitée
AQtime est un profileur professionnel très puissant pour Delphi (et d'autres langages).
Fonctionnalités :
- Profilage de performance détaillé
- Analyse de mémoire et détection de fuites
- Profilage de l'utilisation de ressources
- Nombreuses vues et rapports
- Intégration avec l'IDE Delphi
Avantages :
- Très complet et précis
- Interface utilisateur riche
- Support professionnel
- Idéal pour les projets d'entreprise
Inconvénients :
- Payant (coût élevé)
- Courbe d'apprentissage
- Peut ralentir significativement l'application pendant le profilage
Quand l'utiliser :
Pour des projets professionnels où les performances sont critiques et où le budget le permet.
Une alternative gratuite qui offre un profilage de base.
Avantages :
- Gratuit
- Interface simple
- Suffisant pour des besoins basiques
Inconvénients :
- Moins de fonctionnalités qu'AQtime
- Documentation limitée
- Support communautaire
Performance Monitor (PerfMon) : Outil Windows natif pour surveiller les performances système.
Process Explorer : Outil gratuit de Sysinternals (Microsoft) pour analyser les processus en détail.
Visual Studio Profiler : Peut être utilisé avec des applications Delphi si vous avez Visual Studio.
Vous pouvez créer votre propre système de profilage simple :
unit ProfilageSimple;
interface
uses
System.SysUtils, System.Diagnostics, System.Generics.Collections;
type
TProfileurSimple = class
private
FMesures: TDictionary<string, Int64>;
FChronos: TDictionary<string, TStopwatch>;
public
constructor Create;
destructor Destroy; override;
procedure Commencer(const Nom: string);
procedure Terminer(const Nom: string);
procedure AfficherResultats;
end;
var
Profileur: TProfileurSimple;
implementation
constructor TProfileurSimple.Create;
begin
FMesures := TDictionary<string, Int64>.Create;
FChronos := TDictionary<string, TStopwatch>.Create;
end;
destructor TProfileurSimple.Destroy;
begin
FMesures.Free;
FChronos.Free;
inherited;
end;
procedure TProfileurSimple.Commencer(const Nom: string);
var
Chrono: TStopwatch;
begin
Chrono := TStopwatch.StartNew;
FChronos.AddOrSetValue(Nom, Chrono);
end;
procedure TProfileurSimple.Terminer(const Nom: string);
var
Chrono: TStopwatch;
Temps: Int64;
begin
if FChronos.TryGetValue(Nom, Chrono) then
begin
Chrono.Stop;
Temps := Chrono.ElapsedMilliseconds;
if FMesures.ContainsKey(Nom) then
FMesures[Nom] := FMesures[Nom] + Temps
else
FMesures.Add(Nom, Temps);
FChronos.Remove(Nom);
end;
end;
procedure TProfileurSimple.AfficherResultats;
var
Paire: TPair<string, Int64>;
begin
WriteLn('=== Résultats du profilage ===');
for Paire in FMesures do
WriteLn(Format('%s : %d ms', [Paire.Key, Paire.Value]));
end;
initialization
Profileur := TProfileurSimple.Create;
finalization
Profileur.AfficherResultats;
Profileur.Free;
end.Utilisation :
procedure MonTraitement;
begin
Profileur.Commencer('Chargement données');
ChargerDonnees;
Profileur.Terminer('Chargement données');
Profileur.Commencer('Traitement');
TraiterDonnees;
Profileur.Terminer('Traitement');
Profileur.Commencer('Sauvegarde');
SauvegarderResultats;
Profileur.Terminer('Sauvegarde');
end;Lors de l'analyse des résultats de profilage, concentrez-vous sur :
Les fonctions "chaudes" (hot spots) : Les fonctions qui consomment beaucoup de temps d'exécution (généralement les 5-10 premières du rapport).
Les fonctions appelées très fréquemment : Une fonction rapide appelée des millions de fois peut être un problème.
Les boucles imbriquées : Particulièrement celles avec une complexité O(n²) ou pire.
Les accès aux bases de données : Souvent la cause principale de lenteur dans les applications.
Les allocations/libérations mémoire fréquentes : La création et destruction répétée d'objets peut ralentir l'application.
Les opérations de chaînes de caractères : La concaténation répétée de chaînes est coûteuse.
Supposons que le profileur vous montre :
Fonction Temps % Appels
================================================
ChargerClients 2500ms 50% 1
RechercherClient 1000ms 20% 50000
AfficherInterface 500ms 10% 1
CalculerTotal 400ms 8% 10000
ValiderEmail 300ms 6% 5000
Autres 300ms 6% -
Analyse :
-
ChargerClientsprend 50% du temps. C'est votre priorité n°1 d'optimisation. -
RechercherClientest appelée 50 000 fois. Même si chaque appel est rapide (~0.02ms), le total est significatif. Peut-être faut-il réduire le nombre d'appels ou utiliser une meilleure structure de données. -
CalculerTotaletValiderEmailméritent aussi d'être examinées mais avec moins de priorité.
Une fois un goulot identifié :
1. Comprendre pourquoi c'est lent
Examinez le code de la fonction. Utilisez TStopwatch pour mesurer ses différentes parties et identifier précisément où le temps est perdu.
2. Chercher les opérations coûteuses
- Accès base de données non optimisés
- Boucles inefficaces
- Allocations mémoire inutiles
- Conversions de type répétées
- Opérations sur les chaînes
3. Vérifier les appels multiples
Parfois le problème n'est pas la fonction elle-même, mais le fait qu'elle est appelée trop souvent. Peut-être pouvez-vous mettre en cache certains résultats ?
Une fois les goulots identifiés, voici les techniques courantes pour optimiser votre code Delphi.
Choisir la bonne structure de données :
// LENT : Recherche linéaire dans une liste
function TrouverClient(Liste: TList<TClient>; ID: Integer): TClient;
var
Client: TClient;
begin
Result := nil;
for Client in Liste do
begin
if Client.ID = ID then
begin
Result := Client;
Break;
end;
end;
end;
// RAPIDE : Utiliser un dictionnaire
var
Clients: TDictionary<Integer, TClient>;
function TrouverClientRapide(ID: Integer): TClient;
begin
Clients.TryGetValue(ID, Result);
end;Éviter les boucles imbriquées inutiles :
// LENT : O(n²)
procedure TrouverDoublons(Liste: TList<Integer>);
var
i, j: Integer;
begin
for i := 0 to Liste.Count - 1 do
for j := i + 1 to Liste.Count - 1 do
if Liste[i] = Liste[j] then
ShowMessage('Doublon trouvé');
end;
// RAPIDE : O(n)
procedure TrouverDoublonsRapide(Liste: TList<Integer>);
var
Vus: TDictionary<Integer, Boolean>;
Valeur: Integer;
begin
Vus := TDictionary<Integer, Boolean>.Create;
try
for Valeur in Liste do
begin
if Vus.ContainsKey(Valeur) then
ShowMessage('Doublon trouvé')
else
Vus.Add(Valeur, True);
end;
finally
Vus.Free;
end;
end;Éviter la concaténation répétée :
// LENT : Chaque += crée une nouvelle chaîne
function GenererRapport(Lignes: TStringList): string;
var
Ligne: string;
begin
Result := '';
for Ligne in Lignes do
Result := Result + Ligne + #13#10; // Très inefficace !
end;
// RAPIDE : Utiliser TStringBuilder
function GenererRapportRapide(Lignes: TStringList): string;
var
Builder: TStringBuilder;
Ligne: string;
begin
Builder := TStringBuilder.Create;
try
for Ligne in Lignes do
Builder.AppendLine(Ligne);
Result := Builder.ToString;
finally
Builder.Free;
end;
end;Utiliser les bonnes fonctions de comparaison :
// Pour comparaisons insensibles à la casse
if SameText(Chaine1, Chaine2) then // Plus rapide que UpperCase()
// ...
// Pour vérifier si une chaîne commence par
if Chaine.StartsWith('Bonjour') then // Plus rapide que Pos()
// ...Mettre en cache les calculs coûteux :
type
TCalculateurCache = class
private
FCacheResultats: TDictionary<string, Double>;
public
constructor Create;
destructor Destroy; override;
function CalculerComplexe(const Param: string): Double;
end;
function TCalculateurCache.CalculerComplexe(const Param: string): Double;
begin
// Vérifier si déjà calculé
if FCacheResultats.TryGetValue(Param, Result) then
Exit; // Retourner la valeur en cache
// Sinon, effectuer le calcul coûteux
Result := CalculComplexeEtLong(Param);
// Mettre en cache pour les prochaines fois
FCacheResultats.Add(Param, Result);
end;Réduire le nombre de requêtes :
// LENT : Une requête par client
procedure ChargerCommandesLent(Clients: TList<TClient>);
var
Client: TClient;
begin
for Client in Clients do
begin
// Requête SQL pour chaque client
ChargerCommandesDuClient(Client.ID);
end;
end;
// RAPIDE : Une seule requête pour tous
procedure ChargerCommandesRapide(Clients: TList<TClient>);
var
IDsClients: string;
begin
// Créer une liste d'IDs : '1,2,3,4,5'
IDsClients := CreerListeIDs(Clients);
// Une seule requête SQL avec IN
ExecuterSQL('SELECT * FROM Commandes WHERE ClientID IN (' + IDsClients + ')');
end;Utiliser des index appropriés : Assurez-vous que vos tables ont des index sur les colonnes fréquemment recherchées.
Limiter les colonnes récupérées :
// LENT : Récupérer toutes les colonnes
SELECT * FROM Clients WHERE ...
// RAPIDE : Récupérer uniquement ce qui est nécessaire
SELECT ID, Nom, Prenom FROM Clients WHERE ...Utiliser les transactions pour les opérations multiples :
procedure InsererPlusieursFois;
begin
// Démarrer une transaction
FDConnection.StartTransaction;
try
// Insérer 1000 enregistrements
for i := 1 to 1000 do
InsererEnregistrement(i);
// Valider toutes les insertions en une fois
FDConnection.Commit;
except
FDConnection.Rollback;
raise;
end;
end;Suspendre les mises à jour visuelles :
procedure RemplirListeRapide;
begin
ListView1.Items.BeginUpdate;
try
for i := 1 to 10000 do
AjouterElementListe(i);
finally
ListView1.Items.EndUpdate;
end;
end;Virtualiser les contrôles pour grandes quantités de données :
Utilisez des composants virtuels (comme TListView en mode virtuel) qui ne créent que les éléments visibles à l'écran.
Charger les images en arrière-plan :
procedure ChargerImageAsync(const URL: string);
begin
TTask.Run(procedure
var
Image: TBitmap;
begin
// Télécharger/charger l'image (opération longue)
Image := TelechargerImage(URL);
// Mettre à jour l'interface dans le thread principal
TThread.Synchronize(nil, procedure
begin
Image1.Picture.Assign(Image);
Image.Free;
end);
end);
end;Réutiliser les objets plutôt que les recréer :
// LENT : Créer/détruire à chaque fois
procedure TraiterDonnees;
var
Liste: TStringList;
begin
for i := 1 to 1000 do
begin
Liste := TStringList.Create;
try
// Traiter...
finally
Liste.Free;
end;
end;
end;
// RAPIDE : Réutiliser
procedure TraiterDonneesRapide;
var
Liste: TStringList;
begin
Liste := TStringList.Create;
try
for i := 1 to 1000 do
begin
Liste.Clear;
// Traiter...
end;
finally
Liste.Free;
end;
end;Utiliser les objets de taille appropriée :
// Dimensionner correctement les listes
Liste := TList<string>.Create;
Liste.Capacity := 1000; // Pré-allouer si on connaît la taille Pour les tâches qui peuvent s'exécuter en parallèle :
uses
System.Threading;
procedure TraiterEnParallele;
begin
TParallel.For(0, 999, procedure(i: Integer)
begin
// Traiter l'élément i en parallèle
TraiterElement(i);
end);
end;Attention : Le multithreading ajoute de la complexité. N'utilisez-le que si le gain de performance en vaut la peine.
Activer l'optimisation du compilateur :
Dans Project > Options > Delphi Compiler > Compiling :
- Cochez Optimization pour le mode Release
- Décochez Debug information en Release (réduit la taille de l'exécutable)
Utiliser les directives de compilation :
{$OPTIMIZATION ON} // Activer optimisation pour cette unité
{$RANGECHECKS OFF} // Désactiver les vérifications de bornes (attention !)
{$OVERFLOWCHECKS OFF} // Désactiver les vérifications de débordementVersions courtes équivalentes : {$O+}, {$R-}, {$Q-}.
Attention : Désactiver les vérifications peut masquer des bugs (accès tableau hors bornes, overflow d'entiers). Faites-le uniquement sur du code éprouvé et testé en profondeur.
Ne jamais optimiser sans avoir d'abord mesuré. Vous pourriez perdre du temps sur des parties qui n'ont aucun impact réel.
Concentrez-vous sur les fonctions qui représentent le plus de temps d'exécution. Optimiser une fonction qui prend 0,1% du temps n'a quasiment aucun impact.
N'optimisez pas au détriment de la lisibilité sauf nécessité absolue. Un code maintenable est plus important qu'un code ultra-optimisé mais incompréhensible.
// Préférez ceci (clair)
function CalculerMoyenne(Valeurs: TArray<Integer>): Double;
var
Somme, i: Integer;
begin
Somme := 0;
for i := 0 to High(Valeurs) do
Somme := Somme + Valeurs[i];
Result := Somme / Length(Valeurs);
end;
// Plutôt que ceci (obscur mais marginalement plus rapide)
function CalculerMoyenne(V: TArray<Integer>): Double;
var S,i:Integer;begin S:=0;for i:=0to High(V)do S:=S+V[i];Result:=S/Length(V);end; Après avoir optimisé quelque chose :
- Vérifiez que ça fonctionne toujours correctement (tests unitaires)
- Mesurez l'amélioration réelle
- Si le gain est négligeable, annulez l'optimisation
Si vous écrivez du code optimisé qui n'est pas évident, ajoutez un commentaire expliquant pourquoi :
// Utilisation de TStringBuilder plutôt que concaténation
// car cette fonction peut traiter jusqu'à 100 000 lignes
// (gain mesuré : 5000ms → 200ms)
function GenererGrosRapport: string;
var
Builder: TStringBuilder;
begin
// ...
end;La citation célèbre de Donald Knuth : "L'optimisation prématurée est la racine de tous les maux."
Développez d'abord un code propre et fonctionnel. Optimisez seulement quand :
- Vous avez identifié un problème de performance réel
- Vous avez mesuré où est le problème
- L'optimisation apporte un gain significatif
Un code parfait pour 100 éléments peut être catastrophique pour 100 000 éléments. Pensez à comment votre code se comportera avec de grandes quantités de données.
// N'optimisez PAS ce genre de choses
// (le compilateur le fait déjà)
Result := X * 2; // Vs Result := X + X; // Aucune différence réelleN'écrivez pas du code incompréhensible juste pour gagner quelques microsecondes.
Il est inutile d'optimiser chaque ligne de code. Concentrez-vous sur les 20% qui comptent vraiment.
Une optimisation qui casse des fonctionnalités ne vaut rien. Testez toujours après avoir optimisé.
Ajoutez des journaux de performance dans votre application en production :
procedure TraiterCommande(Commande: TCommande);
var
Chrono: TStopwatch;
begin
Chrono := TStopwatch.StartNew;
try
// Traitement...
TraiterLaCommande(Commande);
finally
Chrono.Stop;
Logger.Log(Format('Commande %d traitée en %d ms',
[Commande.ID, Chrono.ElapsedMilliseconds]));
end;
end;Pour les applications professionnelles, envoyez des métriques de performance à un système de monitoring pour détecter les problèmes en production.
Configurez des alertes si certaines opérations dépassent un temps acceptable :
procedure TraiterAvecAlerte(Temps: Int64);
begin
if Temps > 5000 then // Plus de 5 secondes
EnvoyerAlerte('Traitement anormalement lent : ' + IntToStr(Temps) + ' ms');
end;Lorsque vous optimisez une application, suivez cette checklist :
□ Phase 1 : Mesure
- Identifier les fonctionnalités lentes (feedback utilisateurs)
- Profiler l'application
- Identifier les 3-5 plus gros goulots d'étranglement
□ Phase 2 : Analyse
- Comprendre pourquoi chaque goulot est lent
- Estimer le gain potentiel d'optimisation
- Prioriser les optimisations par impact/effort
□ Phase 3 : Optimisation
- Optimiser le goulot #1
- Tester que tout fonctionne encore
- Mesurer l'amélioration réelle
- Répéter pour les autres goulots si nécessaire
□ Phase 4 : Validation
- Exécuter tous les tests unitaires
- Tester l'application complète
- Vérifier les performances en conditions réelles
- Documenter les optimisations effectuées
Utilisez d'abord TStopwatch pour identifier les parties lentes de votre code avant d'investir dans des outils complexes.
Concentrez-vous sur les optimisations qui ont un impact visible. Gagner 1ms sur une opération qui prend déjà 2ms n'aura aucun impact perceptible.
Avec l'expérience, vous apprendrez à reconnaître les structures de code susceptibles d'être lentes (boucles imbriquées, requêtes dans des boucles, etc.).
Avant d'optimiser, sauvegardez votre code (utilisez Git). Vous pourrez ainsi revenir en arrière si l'optimisation cause des problèmes.
Parfois, améliorer la perception de performance (barre de progression, retour immédiat) est plus important qu'optimiser le code lui-même.
Parfois, la meilleure optimisation consiste à accepter qu'une opération prenne du temps et à l'exécuter en arrière-plan plutôt que d'essayer de l'accélérer.
Problème : Une grille avec 10 000 lignes prend 30 secondes à charger.
Investigation :
- Profiler montre que 90% du temps est dans la boucle de remplissage
- Chaque ajout de ligne déclenche un rafraîchissement visuel
Solution :
// AVANT
for i := 0 to 9999 do
AjouterLigne(Donnees[i]); // Rafraîchit à chaque ligne
// APRÈS
StringGrid1.BeginUpdate;
try
for i := 0 to 9999 do
AjouterLigne(Donnees[i]);
finally
StringGrid1.EndUpdate; // Rafraîchit une seule fois
end;Résultat : Temps réduit de 30s à 0.5s.
Problème : Rechercher un client par ID prend de plus en plus de temps au fur et à mesure que la liste grandit.
Investigation :
- 50 000 clients dans une TList
- Recherche linéaire O(n) utilisée
Solution :
// AVANT
FClients: TList<TClient>;
function TrouverClient(ID: Integer): TClient;
var
Client: TClient;
begin
for Client in FClients do
if Client.ID = ID then
Exit(Client);
Result := nil;
end;
// APRÈS
FClients: TDictionary<Integer, TClient>;
function TrouverClient(ID: Integer): TClient;
begin
FClients.TryGetValue(ID, Result);
end;Résultat : Temps de recherche de O(n) à O(1), passage de ~25ms à <0.1ms.
Problème : Générer un rapport PDF de 100 pages prend 2 minutes.
Investigation :
- La concaténation de chaînes prend 80% du temps
- Des milliers de petites concaténations
Solution :
// AVANT
function GenererRapport: string;
var
i: Integer;
begin
Result := '';
for i := 1 to 100000 do
Result := Result + Ligne[i]; // Crée une nouvelle chaîne à chaque fois
end;
// APRÈS
function GenererRapport: string;
var
Builder: TStringBuilder;
i: Integer;
begin
Builder := TStringBuilder.Create(100000 * 80); // Pré-allocation
try
for i := 1 to 100000 do
Builder.Append(Ligne[i]);
Result := Builder.ToString;
finally
Builder.Free;
end;
end;Résultat : Temps réduit de 120s à 8s.
L'optimisation des performances est un aspect crucial du développement professionnel avec Delphi. Les points essentiels à retenir :
Méthodologie :
- Mesurez toujours avant d'optimiser
- Utilisez le profilage pour identifier les vrais problèmes
- Concentrez vos efforts sur les goulots d'étranglement significatifs
- Testez après chaque optimisation
Techniques clés :
- Choisissez les bonnes structures de données et algorithmes
- Optimisez les opérations sur les chaînes avec
TStringBuilder - Mettez en cache les résultats de calculs coûteux
- Minimisez les accès aux bases de données
- Suspendez les mises à jour visuelles lors d'opérations groupées
Principes :
- La lisibilité du code prime sur les micro-optimisations
- L'optimisation prématurée est à éviter
- Documentez vos optimisations non évidentes
- Pensez à l'échelle dès la conception
Outils :
TStopwatchpour les mesures simples- Profileurs intégrés ou tiers pour l'analyse approfondie
- Tests unitaires pour valider les optimisations
- Journalisation pour surveiller en production
En maîtrisant ces concepts et techniques, vous serez capable de créer des applications Delphi non seulement fonctionnelles, mais aussi performantes et réactives. N'oubliez pas que l'optimisation est un processus itératif : mesurez, optimisez, validez, et répétez jusqu'à atteindre les performances souhaitées.