Optimiser les Performances des Applications Mac OS X
Optimisation : La Théorie | Première Etape : Développer pour Mac OS X | Deuxième Etape : Mesures et Optimisation Ciblées | Une Méthodologie pour Améliorer les Performances | Optimisation : La Pratique | Minimiser le Temps de Lancement d’une Application | Optimiser les Fonctions Principales de votre Application | Optimiser l’Utilisation de la Mémoire | Optimiser les Opérations Graphiques | Optimiser les Opérations de Rendu des Textes
Cet article est à propos des performances — comment y penser, comment les mesurer et comment les améliorer. Bien sûr, la performance est un sujet complexe et Apple s’est doté d’un ensemble solide de documentations disponibles sur ce sujet. Cet article complète cette documentation en vous donnant un canevas pour toujours avoir la problématique de la performance à l’esprit ainsi que des astuces que vous pourrez utiliser immédiatement.
L’optimisation des performances de votre application est un processus en deux étapes. La première étape implique de vérifier que vous utilisez les techniques de programmation les mieux adaptées à votre architecture Mac OS X. La seconde consiste à décider quelles sont les mesures de performance importantes et à vérifier que votre application répond à ces mesures. Apple fournit une suite d’applications de profilages divers et des outils ligne de commande pour vous aider dans cette tâche.

ObjectAlloc : Analyse les allocations mémoire des objets de l’application.
Mac OS X, avec sa mémoire virtuelle et son multitâche préemptif, est structurellement différent de toutes les versions précédentes de Mac OS. Cela signifie que certaines pratiques de programmation qui étaient valables sous Mac OS 9 ne le sont plus sous Mac OS X. La première étape vers l’optimisation des performances de votre application consiste à coder (ou, dans le cas d’applications Carbon existantes, à recoder) des tâches de la manière la plus efficace pour Mac OS X.
Voici les plus importantes modifications spécifiques Mac OS X que vous devriez apporter à votre application. Pour plus de détails, voir le livre Performance.
- Eliminer les sondages. Sous Mac OS 9, il était acceptable d’attendre certains événements en exécutant une boucle vide jusqu’à ce que l’événement intervienne. Dans l’environnement multitâche de Mac OS X, cela représente une perte de temps et de ressources inacceptable. A la place, soyez conduit par les événements et écrivez des routines qui seront appelées au moment où l’événement souhaité les déclenchera. POur les applications Carbon, cela signifie d’utiliser le Carbon Event Manager.
- Réduire l’espace de travail. Mac OS X optimise l’utilisation de la mémoire en déplaçant du code du disque vers la mémoire quand le besoin se fait sentir et en supprimant le code qui n’est pas fréquemment utilisé. A tout moment, le nombre de pages de mémoire virtuelle que l’application utilise est appelé l’espace de travail. Lorsque vous êtes sur le point de terminer votre application, vous pouvez utiliser une technique de réorganisation manuelle du code appelée scatter loading (chargement de petites quantités) pour minimiser l’espace de travail de votre application. Reportez-vous au paragraphe “Améliorer la Localisation des Références” du livre Performance pour plus de détails.
- Produire un binaire Mach-O. Le format exécutable Mach-O remplace l’ancien format PEF moins performant couramment utilisé sous Mac OS 9. Si votre application tourne sous Mac OS X, vous devriez la produire au format Mach-O.
- Liez à l’avance votre application. Quand votre application se lance, Mac OS X doit prendre le temps de “lier” des références indéfinies de votre code à des localisations correctes de l’environnement d’exécution. En liant à l’avance votre application lors du processus de construction, vous pouvez diminuer son temps de lancement. Pour plus de détails, se reportez aux “Apple Release Notes” disponibles sur le “prebinding”.
Les modifications de code que vous avez effectuées lors de la Première Etape sont importantes mais c’est ici que vous allez passer la plus grande partie de votre temps. Gardez en mémoire que vous ne pouvez pas améliorer les performances si vous ne les avez pas mesurées une première fois et vous ne pouvez pas savoir si vous en avez fini tant que vous n’avez pas fixé des objectifs à vos mesures.
Intégrer les Mesures dans le Processus de Développement
Il est important que vous fassiez du processus de mesure et d’amélioration des performances applicatives une partie intégrale de votre cycle de développement. Voici un plan en quatre étapes pour vous aider à démarrer :
Premièrement, décidez quelles sont les fonctions dont la performance est importante à vos yeux (voir quelques suggestions après).
Deuxièmement, pour chaque fonction, choisissez un objectif approprié de performance (par exemple, un temps de lancement inférieur à une seconde). Vous souhaiterez aussi peut-être évaluer les produits concurrents et égaler ou dépasser leur performance.
Troisièmement, concevez dès le début du processus de développement une procédure de mesure de chaque performance, en utilisant soit du code personnel soit un outil externe.
Quatrièmement, comparez régulièrement chaque mesure prise sur votre application et résolvez rapidement les problèmes de performance. Certaines équipes de développement établissent une politique de refus de tout code qui empêche l’application d’atteindre ses objectifs de performance.
Choisir ses Mesures
Vous obtiendrez les meilleures performances si vous vous concentrez sur les mesures suivantes :
- Minimiser le temps de lancement.
- Optimiser les performances d’affichage et de réaffichage temps réel (l’affichage qui intervient lorsque l’utilisateur redimensionne interactivement des éléments visuels — la largeur d’une colonne ou la fenêtre de votre application, par exemple).
- Optimiser les performances des fonctions primaires de votre application (en fait, les opérations qui constituent le travail principal que votre application effectue pour l’utilisateur).
- Minimiser l’utilisation des ressources système de votre application, y compris l’utilisation CPU, la taille globale du code et la taille de votre espace de travail.
Une fois que vous avez choisi vos critères et conçu les manières de les mesurer, comment faites vous pour améliorer les performances de votre application ? Beaucoup de développeurs répètent la boucle mesure/analyse/recodage jusqu’à ce que tous les critères de performance atteignent l’objectif fixé. “Mesure” signifie utiliser des outils de performance pour rassembler des données sur la manière dont votre application passe son temps et consumme les ressources systèmes, et l’endroit où cela se passe. “Analyse” veut dire analyser les données afin d’y trouver des goulots de temps et de ressources qui font que l’application tombe en dessous des attentes de mesure. “Recodage” implique de concevoir une approche de réduction ou d’ellimination de ces goulots et de l’implémenter.
L’application de cette méthodologie tout au long du processus de développement réduira la quantité de travail que vous aurez à accomplir.
Techniques pour Améliorer les Performances
Gardez à l’esprit les techniques suivantes au moment de concevoir des solutions à vos goulots d’étranglement de performance :
- Soyez parresseux. N’alouez aucune mémoire, aucun code et ne consommez aucune ressource tant que les actions de l’utilisateur ne requièrent pas que vous le fassiez.
- Soyez économe. N’utilisez que les ressources dont vous avez besoin et rien de plus.
- Soyez imaginatif. Si une action prend trop de temps, cherchez une manière de la rendre plus rapide.
- Optimisez pour l’utilisateur moyen. Ne pénalisez pas 80% de vos utilisateurs par des fonctions utilisées que par les autres 20%. Plutôt, écrivez deux versions de votre code : une version plus petite et plus rapide qui gère les besoins de la plupart des utilisateurs et une version plus lourde qui ne sera appelée qu’un moment opportun.
- A chaque fois que possible, mettez en cache et réutilisez au lieu de calculer. Cela s’applique aux fichiers en lecture seule, aux images, aux calculs souvent effectuées, aux menus créés dynamiquement et aux autres données réutilisées.
Voici un exemple avec ces techniques en cation. Lorsque vous essayez de réduire le temps de lancement de votre application, observez bien tout le contenu de votre fichier nib principal. Quand votre application se lance, Mac OS X doit charger le fichier nib principal avant de pouvoir afficher quoi que ce soit à l’écran. Le chargement d’un fichier nib peut être très consommateur ; chaque objet du fichier nib doit être instancié et initialisé et le processus peut déclencher le chargement d’un framework non-resident. Si une ressource n’est pas requise pour faire passer votre application au point où elle affiche quelque chose à l’écran, vous devriez la déplacer dans un autre fichier nib et le charger après que l’application se soit affichée elle-même.
Les paragraphes qui suivent vous donnent des plans d’action pour cinq des optimisations que vous serez le plus enclain à effectuer.
Critères
Concentrez-vous sur la minimisation de l’intervalle de temps entre le moment où vous lancez l’application et celui où votre application finit de dessiner toutes ses fenêtres visibles.
Outils
L’outil à ligne de commande sample prend périodiquement un instantanné de la pile d’appel et, à la fin de la période d’échantillonage, affiche les temps passés par votre application sur chacune de ses fonctions. Utilisez cet outil pour déterminer l’endroit où votre application passe le plus de temps.
L’outil fs_usage affiche des statistiques d’utilisation des appels système relatives à l’activité au système de fichier. Examinez les fichiers et les dossiers accédés en vue d’interrogation. Des enquêtes approfondies sur ces accès peuvent révéler des tâches de haut niveau qui sont exécutées trop tôt ou inutilement.
Techniques
Dans ces cas où vous devez savoir combien de temps passe une routine pour son exécution, vous pouvez utiliser fs_usage pour effectuer cette mesure de manière précise (bien que cela nécessite de modifier légèrement votre code). Ajoutez du code qui “touche” à des chemins d’accès imaginaires — par exemple, stat(”Dessin fenêtre principale : DEBUT”) et stat(”Dessin fenêtre principale : FIN”). Ces tentatives d’I/O apparaîtront, horodatées, dans la sortie de fs_usage. Vous pouvez facilement trouver l’affichage de ces chemins d’accès, calculer la durée à partir des horodatages et revoir tous les I/O que la routine a déclenchés. C’est une technique courante qui est utile dans beaucoup de situations.
Plan d’action
Rechercher les données à partir de sample pour avoir les indiquations des endroits où votre application passe son temps lors de la phase de lancement ; rechercher les fonctions qui consomme beaucoup de temps. De manière similaire, recherchez les données à partir de fs_usage relatives aux fonctions et aux accès fichier que vous ne vous attendiez pas à voir et corriger votre code afin d’éliminer ces appels et accès non nécessaires. Déplacez l’exécution de tout code qui n’est pas absolument nécessaire au processus d’affichage initial des fenêtres après que ces fenêtres aient été rendues visibles. Examinez le code qui est nécessaire au processus de lancement afin d’y trouver des opportunités de diminution du temps d’exécution.
Astuces
Bien que l’outil sample puisse indiquer qu’une fonction prend trop de temps, cela ne vous dit pas si l’exécution de cette fonction est lente ou si elle est exécuté un grand nombre de fois. Utilisez le débogueur gdb pour trouver combien de fois la fonction est appelée.
Rechercez des opportunités de mise en cache et de réutilisation de données. Par exemple, si vous remarquez que votre application explore le contenu d’un répertoire donné, déterminer que le contenu de ce répertoire change rarement. Dans ce cas, reécrivez votre code pour mettre en cache le contenu du répertoire avec un horodatage de dernière modification. Faites en sorte aussi que votre code vérifie l’horodatage de modification du répertoire et utilisez soit les données mises en cache (si le répertoire n’a pas changé) soit les données réelles du répertoire en reécrivant le cache.
Pour les applications Cocoa (et les applications Carbon qui utilisent les nibs), assurez-vous que le fichier du nib principal de votre application ne contient que les ressources requises pour l’affichage initial. Placez tout le reste dans d’autres fichiers nib.
Si vous écrivez une application Cocoa, placez le code nécessaire au lancement dans la routine AwakeFromNib:. Placez le code qui peut être reporté après le démarrage dans la routine ApplicationDidFinishLaunching:.
Critères
Là, vous devez décider quelles sont les fonctions principales de votre application que vous allez mesurer. Par exemple, dans un programme tableur, vous devez décider que le temps de recalcul du tableau est un critère important. L’intervalle de temps à mesurer est celui qui sépare la fin de l’action appropriée de l’utilisateur du moment où la réponse de votre application est donnée. Examinez les performances des applications concurrentes et fixez votre objectif en rapport.
Outils et Techniques
Utilisez les outils sample et fs_usage, décrits plus haut, pour mesurer combien de temps prennent les fonctions clé à s’exécuter et où la CPU passe son temps.
Plan d’Action
Recherchez les données à partir de sample et de fs_usage, comme décrit plus haut, afin de trouver les fonctions qui consomment beaucoup de temps, ainsi que les fonctions et les accès fichier inattendus. Corriger votre code afin d’éliminer ces appels et accès non nécessaires. Examinez le reste de votre code afin d’y trouver des opportunités de diminution du temps d’exécution.
Astuces
Comme dans le paragraphe précédent, recherchez des opportunités de reporter les opérations qui ne sont pas absolument nécessaires et accélérez les opérations en mettant en cache des données.
Critères
Il y a deux critères à prendre en compte : l’utilisation mémoire de votre application juste après que le lancement soit terminé, et son utilisation durant le temps d’une utilisation normale simulée. Si c’est une application Cocoa, vous devriez aussi examiner ses modèles d’allocation d’objet sur la durée.
Outils
L’utilitaire ligne de commande top (voir copie d’écran ci-dessous) affiche une table périodiquement mise à jour de statistiques d’utilisation de la CPU et de la mémoire pour chaque processus du système. Vous serez avant tout intéressé par la colonne appelée RPRVT, ce qui signifie “process Resident PRiVaTe memory” (”mémoire privée résidente du processus”) — en fait, la quantité de mémoire que chaque processus est en train d’utiliser.
Utilisez l’outil ligne de commande (ci-dessous) pour trouver les buffers qui sont alloués mais qui ne sont pas référencés par votre programme.

Statistiques d’utilisation mémoire et CPU des processus système.

Affichage des buffers alloués mais non référencés par un programme
Utilisez l’application MallocDebug pour analyser comment votre application utilise la mémoire et pour trouver les fuites de mémoire.
Pour les applications Cocoa, vous pouvez utiliser l’application ObjectAlloc (voir la copie d’écran au début de cet article) pour pister sur la durée comment l’application alloue de la mémoire à ses objets.
Plan d’Action
Votre première étape devrait consister en l’utilisation des outils décrits plus haut pour détecter et éliminer les fuites évidentes de mémoire. Une fois que vous en avez terminé avec cela, la tâche qui reste à faire — améliorer l’utilisation mémoire de votre application jusqu’à ce qu’elle atteigne vos objectifs de performance — est importante mais elle requiert de l’assuidité et du jugement afin de déterminer quand vous en avez fini.
Bien que vous puissiez facilement obtenir des mesures (chiffres) de l’utilisation mémoire, il est plus difficile d’établir des objectifs précis pour ces mesures. Le mieux que vous puissiez faire est une comparaison réelle des mesures de votre application avec celles d’une application connue pour avoir une bonne utilisation de la mémoire (l’implémentation Mac OS X de TextEdit est un bon exemple). Vous devriez aussi analyser les chiffres retournés par les outils décrit au dessus pour y détecter des indications de problèmes possibles.
L’exemple suivant devrait vous donner une idée de la manière dont cette approche fonctionne. Supposez que vous vérifiiez la valeur PRPVT de TextEdit immédiatement après son lancement et que vous trouviez qu’il utilise 700 Ko de mémoire. Vous lancez votre application et comparez sa complexité visuelle à celle de TextEdit. Supposez que votre application utilise, par exemple, 4 Mo de mémoire. Evidemment, vous vous attendriez à ce que votre application, étant visuellement plus complexe, requiert plus de mémoire que TextEdit. La question à vous poser est de savoir si les éléments d’interface supplémentaires que votre application a affiché initialement peuvent raisonnablement compter dans les 3,3 Mo de mémoire additionnelle. Si vous ne pensez pas que le surplus de mémoire est raisonnable, vous devriez alors analyser votre application pour déterminer quel est le code qui utilise la mémoire extra et si l’exécution de ce code peut être oui ou non reportée ou éliminée.
Astuces
La recherche de problèmes mémoire potentiels et trouver leurs causes sont des tâches difficiles. Vous aurez besoin de patience, de discipline et d’une bonne expérience du fonctionnement de vos outils. Comme toujours, vérifiez le livre Performance pour trouver des informations qui puissent vous aider dans cette tâche d’optimisation.
En observant les allocations mémoire qui interviennent, vous pouvez déduire quelles sections de votre code sont en cours d’exécution. A partir de là, vous pouvez peut-être découvrir des opportunités de report ou d’élimination de parties de code. En particulier, il se peut qu’il y ait des alternatives à certaines allocations mémoire qui interviennent dans des boucles, surtout dans des boucles imbriquées.
Si vous optimisez une application Cocoa, regardez les effets des objets auto-libérés, surtout dans les boucles imrbiquées. Dans certaines situations, un grand nombre d’objets auto-libérés peut augmenter la taille de l’”autorelease pool” (espace où sont stockés les objets auto-détruits), ce qui peut être à l’origine d’une utilisation significative de la mémoire tant que ce bassin n’est pas vidé. Vous pouvez prévenir une telle situation en faisant des allocations et des détructions manuelles de certains objets aux bons endroits.
Optimiser les Opérations Graphiques
Votre application devrait dessiner des images statiques aussi instantanément que possible. Elle devrait aussi dessiner les images animées suffisamment fréquemment pour que le mouvement semble fluide. Vous pouvez voir que vous avez des problèmes en observant des délais de dessin notables durant une utilisation normale de votre application.
Outils
Comme décrit auparavant, vous pouvez utiliser fs_usage et l’horodatage pour mesurer le temps des opérations de dessin. Vous pouvez aussi utiliser l’application QuartzDebug (ci-dessous) pour mettre en évidence les régions de l’écran qui sont sur le point d’être mises à jour.

Quartz Debug permet de mettre en évidence les zones graphiques de l’écran qui sont modifiées.
Plan d’Action
D’abord, lancez votre programme en même temps que QuartzDebug et analysez les mises à jour de régions dans des situations qui indiquent que votre code de dessin n’est pas aussi efficace qu’il le devrait. En particulier, observez les régions qui sont mises à jour plusieurs fois, celles qui sont mises à jour même lorsque leur contenu reste inchangé et celles qui sont mises à jour lorsqu’une partie plus petite de la région change réellement.
Ensuite, examinez la vitesse des opérations de dessin de votre application en mode normal d’utilisation, comme décrit dans la partie Critères, au-dessus. Utilisez les outils sample et fs_usage pour rechercher des indices qui mettent en évidence le code qui peut être éliminé ou reporté.
La faculté de réponse de votre application lors des opérations de redimensionnement (de fenêtres ou de colonnes de table, par exemple) est toujours importante. Si de telles opérations sont trop lentes, envisagez différentes manières de simplifier le processus qui a la charge de redessiner pour rendre le redimensionnement temps réel plus sensible.
Astuces
Lorsque votre application Cocoa redessine souvent deux petites régions orientée diagonalement l’une par rapport à l’autre, le système de vue Cocoa peut donner l’ordre à votre application de redessiner un seul grand rectangle qui renferme ces deux petites régions au lieu de les redessiner individuellement. Si cela arrive, vous pourrez améliorer les performances de votre application en forçant le système à redessiner les deux petites régions. La manière d’y arriver varie en fonction de la situation. Une chose que vous pouvez essayer consiste à changer la fréquence selon laquelle chaque petite région est redessinée (par exemple, en alternant les régions qui sont redessinées lors de cycles successifs de dessin).
Au lieu de redessiner une région donnée chaque fois, vous souhaiterez peut-être vérifier qu’elle a changé et ne la redessiner que si c’est nécessaire.
Ne gâchez pas de ressources CPU en redessinant une image plus souvent que nécessaire. Un taux de rafraîchissement de 20 fois par seconde est en général suffisant, vous pourrez même trouver que des taux inférieurs sont aussi acceptables.
Si vous avez optimisé votre application autant que vous le pouviez et n’avez toujours pas atteint vos objectifs de performance, vous pouvez essayer quelques trucs pour rendre votre phase de dessin plus sensible. Ces genres de trucs impliquent de simplifier le processus de dessin en empruntant des voies que l’utilisateur ne remarquera pas, ou que l’utilisateur remarquera mais qu’il trouvera acceptables. Par exemple, lorsque c’est nécessaire, vous pouvez redessiner le contenu de la fenêtre moins fréquemment. Dans le cas du redimensionnement temps réel de la fenêtre de votre application, vous pouvez décider qu’il est acceptable de ne pas redessiner le contenu de la fenêtre tandis que l’utilisateur déplace la poignée de redimensionnement mais de plutôt afficher une image mise en cache lors de cette opération et ne redessiner le contenu de la fenêtre qu’au moment où l’utilisateur relache la poignée.
Critères
La mesure à minimiser est le temps requis pour dessiner du texte. Ce chiffre peut être minimisé en même temps que les étapes d’optimisation de la sensibilité et de dessin graphique.
Outils
Utilisez les outils fs_usage et sample pour mesurer les temps de dessin du texte.
Plan d’Action
Utilisez les données produites par fs_usage et sample pour trouver des opportunités d’augmenter les performances. En plus des optimisations habituelles, vérifiez que vous utilisez les fonctions de dessin de texte les plus simples (et donc les plus rapides) qui accomplissent l’objectif désiré ; voir le paragraphe suivant pour plus de détails.
Astuces
Pour préparer votre application au marché, vous devriez utiliser Unicode pour tout stockage et manipulation. Cependant, soyez averti que les API de manipulation de texte Unicode fournies par Apple ont des performances différentes des API classiques de manipulation de texte ASCII d’Apple, et que vous devez être conscient de ces différences pour augmenter les performances de dessin de texte de votre application. Par exemple, les opérations de mise en forme de texte sont gourmandes, avec pour conséquence que vous devrez utiliser intensivement des objets de mise en forme et de style. En particulier, vous pouvez réutiliser un seul objet de mise en forme pour plusieurs paragraphes simplement en changeant le texte vers lequel il pointe. Lorsque c’est approprié, vous pouvez mettre en cache et réutiliser des objets au lieu de les recréer. Aussi, lorsque vous avez besoin de mesurer la largeur de la chaîne de texte, dans la plupart des cas vous pouvez utiliser la routine ATSUGetGlyphBounds plutôt que les routines ATSUMeasureText et ATSUGetUnjustifiedBounds plus élaborées mais plus gourmandes.

Textes originaux en anglais sur developer.apple.com : Maximizing Mac OS X Applications Performance
Chargement
Commentaires récents