🔝 Retour au Sommaire
Une fois votre application compilée en mode Release, vous pouvez encore améliorer ses performances et réduire sa taille grâce à diverses techniques d'optimisation. L'objectif est d'offrir à vos utilisateurs la meilleure expérience possible : une application rapide, légère et réactive.
L'optimisation est un art délicat qui nécessite un équilibre entre performances, maintenabilité du code et temps de développement. Ce chapitre vous guidera à travers les différentes techniques d'optimisation disponibles avec Delphi 13, en vous aidant à identifier où concentrer vos efforts pour obtenir les meilleurs résultats.
"L'optimisation prématurée est la racine de tous les maux" - Donald Knuth
Avant de commencer à optimiser, suivez toujours cette règle :
- Mesurer : Identifiez où se trouvent réellement les problèmes de performance
- Optimiser : Appliquez des optimisations ciblées
- Mesurer à nouveau : Vérifiez que vos optimisations ont eu l'effet escompté
Ne perdez pas de temps à optimiser du code qui s'exécute rapidement. Concentrez-vous sur les véritables goulots d'étranglement.
Delphi dispose d'outils intégrés pour mesurer les performances :
- Sampling : Permet de voir quelles fonctions consomment le plus de temps CPU
- Instrumentation : Mesure précise du temps d'exécution de chaque fonction
- Compteurs de performances : Pour surveiller mémoire, threads, etc.
Pour accéder au profileur dans Delphi 13 :
- Menu
Exécuter→Paramètres de démarrage→ OngletSampling - Activez le profilage et exécutez votre application
- Analysez les résultats pour identifier les zones lentes
Il existe deux grandes catégories d'optimisations :
Le compilateur Delphi peut automatiquement améliorer votre code. Ces optimisations sont activées en mode Release.
Ce sont les améliorations que vous apportez vous-même au code source pour le rendre plus efficace.
Nous allons explorer les deux approches.
Au-delà des paramètres Release de base, vous pouvez affiner les optimisations du compilateur.
- Allez dans
Projet→Options - Sélectionnez
Compilationsous votre plateforme cible - Assurez-vous d'être en configuration
Release
Optimisation
- Activez cette option pour permettre au compilateur d'optimiser votre code
- Le compilateur réorganisera les instructions pour une exécution plus rapide
Alignement
Options possibles : 1, 2, 4, 8, 16 octets
Recommandé : 8 octets (bon compromis performance/taille)
L'alignement affecte la façon dont les données sont organisées en mémoire. Un bon alignement améliore les performances d'accès à la mémoire.
Vérifications à désactiver en Release
- Contrôle de débordement : Désactivez pour de meilleures performances
- Contrôle de plage : Désactivez sauf si votre application manipule beaucoup de tableaux
- Vérification des E/S : Peut rester activée pour les applications critiques
L'éditeur de liens peut aussi optimiser l'exécutable final :
Élimination du code mort
- Active automatiquement en mode Release
- Supprime les fonctions et unités non utilisées
- Réduit significativement la taille de l'exécutable
Fusion de sections
- Combine les sections similaires de l'exécutable
- Réduit la taille et améliore le chargement
La meilleure optimisation est souvent de choisir le bon algorithme.
Exemple : Recherche dans une liste
❌ Mauvais : Liste simple (O(n))
var
List: TStringList;
i: Integer;
begin
for i := 0 to List.Count - 1 do
begin
if List[i] = 'valeur' then
// Trouvé
end;
end;✅ Meilleur : Dictionnaire (O(1))
var
Dict: TDictionary<string, TObject>;
begin
if Dict.ContainsKey('valeur') then
// Trouvé instantanément
end;Pour 1000 éléments, la version avec dictionnaire peut être 1000 fois plus rapide !
❌ Mauvais : Calcul dans la boucle
for i := 0 to GetCount - 1 do // GetCount appelé à chaque itération
begin
ProcessItem(i);
end;✅ Meilleur : Calcul une seule fois
Count := GetCount; // Appelé une seule fois
for i := 0 to Count - 1 do
begin
ProcessItem(i);
end;procedure TraiterDonnees;
var
Liste: TStringList;
begin
Liste := TStringList.Create;
try
// Utilisation de Liste
Liste.LoadFromFile('data.txt');
// Traitement...
finally
Liste.Free; // Libération immédiate
end;
end;Pour les objets créés/détruits fréquemment, considérez un pool d'objets :
type
TObjectPool<T: class, constructor> = class
private
FPool: TList<T>;
public
function Acquire: T;
procedure Release(AObject: T);
end;Cela évite les allocations/désallocations répétées qui sont coûteuses.
Les chaînes en Delphi utilisent le "copy-on-write", mais certaines opérations forcent des copies :
❌ Mauvais : Concaténations multiples
Result := '';
for i := 0 to 1000 do
Result := Result + IntToStr(i) + ','; // Copie à chaque itération✅ Meilleur : Utiliser TStringBuilder
var
Builder: TStringBuilder;
begin
Builder := TStringBuilder.Create;
try
for i := 0 to 1000 do
Builder.Append(IntToStr(i)).Append(',');
Result := Builder.ToString;
finally
Builder.Free;
end;
end;Les boucles sont souvent des zones critiques pour les performances.
❌ Mauvais
for i := 0 to List.Count - 1 do
begin
if Assigned(List[i]) then
begin
ProcessItem(List[i]);
UpdateUI; // Mise à jour de l'interface à chaque itération !
end;
end;✅ Meilleur
for i := 0 to List.Count - 1 do
begin
if Assigned(List[i]) then
ProcessItem(List[i]);
end;
UpdateUI; // Une seule mise à jour à la fin Pour de très petites boucles avec un nombre connu d'itérations :
// Au lieu de :
for i := 0 to 3 do
Total := Total + Values[i];
// Écrivez :
Total := Values[0] + Values[1] + Values[2] + Values[3];Cela élimine la gestion de la boucle, mais rendez le code moins lisible. À utiliser avec parcimonie.
Pour les petites fonctions appelées fréquemment, utilisez la directive inline :
function Carre(X: Integer): Integer; inline;
begin
Result := X * X;
end;Le compilateur remplacera l'appel de fonction par le code directement, évitant le coût de l'appel.
Attention : N'utilisez inline que pour des fonctions très courtes (1-3 lignes). Pour les fonctions plus longues, cela peut augmenter la taille du code sans gain de performance.
Les appels de méthodes virtuelles sont légèrement plus lents :
type
TBase = class
procedure Process; virtual; // Appel dynamique
end;
TConcret = class(TBase)
procedure Process; override;
end;
// Si vous connaissez le type exact :
var
Obj: TConcret; // Type précis
begin
Obj.Process; // Appel direct possible
end;Pour les opérations sur bases de données :
// Au lieu de valider chaque insertion :
for i := 0 to 1000 do
begin
Query.SQL.Text := 'INSERT INTO ...';
Query.ExecSQL; // Commit automatique à chaque fois
end;
// Utilisez une transaction :
Connection.StartTransaction;
try
for i := 0 to 1000 do
begin
Query.SQL.Text := 'INSERT INTO ...';
Query.ExecSQL;
end;
Connection.Commit; // Un seul commit
except
Connection.Rollback;
raise;
end;Gain de performance : jusqu'à 100 fois plus rapide !
// Au lieu de :
Query.SQL.Text := 'SELECT * FROM Clients'; // Toutes les colonnes
// Chargez seulement ce dont vous avez besoin :
Query.SQL.Text := 'SELECT ID, Nom, Email FROM Clients';Query.SQL.Text := 'SELECT * FROM Clients WHERE Ville = :ville';
Query.Prepare; // Préparation une seule fois
// Puis réutilisez :
Query.ParamByName('ville').AsString := 'Paris';
Query.Open;
// ...
Query.Close;
Query.ParamByName('ville').AsString := 'Lyon';
Query.Open; Lors de modifications multiples de l'interface :
ListView.Items.BeginUpdate;
try
for i := 0 to 1000 do
ListView.Items.Add.Caption := 'Item ' + IntToStr(i);
finally
ListView.Items.EndUpdate; // Rafraîchissement en une fois
end;Ne bloquez jamais l'interface utilisateur :
procedure TForm1.ButtonClick(Sender: TObject);
begin
TTask.Run(
procedure
begin
// Traitement long en arrière-plan
ProcessData;
// Mise à jour de l'interface dans le thread principal
TThread.Synchronize(nil,
procedure
begin
Label1.Caption := 'Terminé';
end
);
end
);
end;Pour afficher de grandes quantités de données :
// Utilisez TListView ou TStringGrid en mode virtuel
ListView.OwnerData := True; // Mode virtuel
procedure TForm1.ListViewData(Sender: TObject; Item: TListItem);
begin
// Fournissez les données à la demande
Item.Caption := GetItemCaption(Item.Index);
end;Seuls les éléments visibles sont créés, économisant mémoire et temps.
Vérifiez les clauses uses de vos unités et supprimez celles qui ne sont pas utilisées :
uses
System.SysUtils, // Nécessaire
System.Classes, // Nécessaire
Vcl.Graphics, // Supprimez si non utilisé
Vcl.Forms; // Nécessaire pour les formulairesDans Projet → Options → Édition de liens :
- Activez Smart linking (liaison intelligente)
- Élimine automatiquement le code non utilisé
- Peut réduire la taille de 20-50%
Utilisez des outils de compression d'exécutables :
- UPX (Ultimate Packer for eXecutables) : Gratuit et efficace
- Peut réduire la taille de 50-70%
- Attention : Certains antivirus peuvent le signaler comme suspect
upx --best MonApplication.exeSupprimez les images, icônes et ressources non utilisées de votre projet.
Delphi 13 introduit l'opérateur ternaire qui peut être plus efficace :
// Avant :
if Condition then
Result := ValeurSiVrai
else
Result := ValeurSiFaux;
// Delphi 13 :
Result := if Condition then ValeurSiVrai else ValeurSiFaux;Delphi 13 améliore les performances de FireDAC :
- Meilleure gestion du cache
- Optimisation des requêtes asynchrones
- Compression native des données
Le nouveau débogueur permet un meilleur profilage :
- Mesures de performance plus précises
- Identification plus rapide des goulots d'étranglement
Selon le principe de Pareto, 80% du temps d'exécution est passé dans 20% du code. Concentrez-vous sur ces 20%.
Quand vous optimisez du code, ajoutez un commentaire expliquant pourquoi :
// Optimisation : Utilisation de TStringBuilder pour éviter
// les copies répétées de chaînes dans la boucle
Builder := TStringBuilder.Create;Cela aide à la maintenance future.
Un code lisible est plus facile à maintenir et à optimiser plus tard. N'écrivez pas de code obscur pour gagner quelques microsecondes.
Testez vos optimisations sur du matériel similaire à celui de vos utilisateurs cibles, pas seulement sur votre machine de développement puissante.
L'optimisation n'est pas une étape unique mais un processus continu :
- Profiler
- Identifier le goulot
- Optimiser
- Mesurer l'amélioration
- Répéter
N'optimisez pas avant d'avoir un problème de performance réel et mesuré.
Ne perdez pas de temps à optimiser du code qui s'exécute une seule fois ou très rarement.
Testez toujours après une optimisation. Un code rapide mais incorrect n'a aucune valeur.
Parfois, réécrire une fonction avec un meilleur algorithme est plus efficace que toutes les micro-optimisations possibles.
"Je pense que ce code est lent" n'est pas une base suffisante. Mesurez avec des outils de profilage.
Avant de distribuer votre application, vérifiez :
- Mode Release activé avec optimisations du compilateur
- Profilage effectué pour identifier les goulots d'étranglement
- Algorithmes critiques optimisés
- Unités inutilisées supprimées
- Smart linking activé
- Tests de performance sur matériel représentatif
- Mémoire : Pas de fuites détectées
- Interface utilisateur : Réactive même sous charge
- Taille de l'exécutable raisonnable
- Temps de démarrage acceptable
- AQtime : Profileur professionnel (payant)
- Sampling Profiler : Intégré à Delphi
- FastMM : Pour détecter les fuites mémoire
- Pascal Analyzer : Analyse statique du code
- Delphi IDE Metrics : Métriques de complexité
- UPX : Compression d'exécutables (gratuit)
Voici des gains de performance typiques obtenus avec différentes optimisations :
| Optimisation | Gain typique | Difficulté |
|---|---|---|
| Compiler en Release | 20-50% | Facile |
| Meilleur algorithme | 100-1000%+ | Moyenne |
| TStringBuilder pour concaténations | 50-200% | Facile |
| Transactions BD | 1000-5000% | Facile |
| Mise en cache | 50-500% | Moyenne |
| Threading pour UI | Réactivité | Moyenne-Difficile |
| Mode virtuel pour listes | 80-95% (mémoire) | Moyenne |
| Smart linking | 20-50% (taille) | Facile |
L'optimisation du code final est un art qui demande de l'expérience et de la mesure. Les règles d'or sont :
- Mesurez d'abord : Utilisez des outils de profilage
- Ciblez les vrais problèmes : Les 20% de code qui prennent 80% du temps
- Choisissez les bons algorithmes : Souvent plus efficace que les micro-optimisations
- Testez après chaque optimisation : Vérifiez que vous avez vraiment amélioré les performances
- Maintenez la lisibilité : Un code maintenable est un code que vous pourrez optimiser plus tard
Avec Delphi 13, vous disposez d'un excellent compilateur qui fait déjà beaucoup d'optimisations automatiquement. Concentrez-vous sur les optimisations à haut niveau (algorithmes, architecture) et laissez le compilateur gérer les détails de bas niveau.
Une application bien optimisée offrira une excellente expérience utilisateur et se démarquera de la concurrence par sa réactivité et son efficacité.