Si quelque chose fait qu'un programme à un seul thread prenne, disons, 10 fois plus de temps qu'il le devrait, vous pouvez exécuter un profileur dessus. Vous pouvez également l'arrêter avec un bouton "pause" et vous verrez exactement ce qu'il fait.
Même s'il n'est que 10% plus lent qu'il ne devrait l'être, si vous l'arrêtez plusieurs fois, vous le verrez bientôt faire à plusieurs reprises la chose inutile. Habituellement, le problème est un appel de fonction quelque part au milieu de la pile qui n'est pas vraiment nécessaire. Cela ne mesure pas le problème, mais il le trouve certainement.
Edit: Les objections supposent principalement que vous ne prenez qu'un échantillon. Si vous êtes sérieux, prenez 10. Toute ligne de code causant un certain pourcentage de gaspillage, comme 40%, apparaîtra sur la pile sur cette fraction d'échantillons, en moyenne. Les goulots d'étranglement (en code mono-thread) ne peuvent pas s'y cacher.
EDIT: Pour montrer ce que je veux dire, de nombreuses objections sont de la forme "il n'y a pas assez d'échantillons, donc ce que vous voyez pourrait être entièrement faux" - des idées vagues sur le hasard. Mais si quelque chose de toute description reconnaissable , non seulement dans une routine ou la routine active, est en vigueur pendant 30% du temps, alors le la probabilité de le voir sur un échantillon donné est de 30%.
Supposons ensuite que 10 échantillons seulement soient prélevés. Le nombre de fois que le problème sera vu dans 10 échantillons suit un distribution binomiale , et la probabilité de le voir 0 fois est de 0,028. La probabilité de le voir 1 fois est de 0,121. Pour 2 fois, la probabilité est de 0,233, et pour 3 fois elle est de 0,267, après quoi elle tombe. Étant donné que la probabilité de le voir moins de deux fois est de 0,028 + 0,121 = 0,139, cela signifie que la probabilité de le voir deux fois ou plus est de 1 - 0,139 = 0,861. La règle générale est que si vous voyez quelque chose que vous pourriez corriger sur deux échantillons ou plus, cela vaut la peine d'être corrigé.
Dans ce cas, la chance de le voir dans 10 échantillons est de 86%. Si vous êtes dans les 14% qui ne le voient pas, prenez simplement plus d'échantillons jusqu'à ce que vous le fassiez. (Si le nombre d'échantillons est porté à 20, la probabilité de le voir deux fois ou plus augmente à plus de 99%.) Donc, il n'a pas été mesuré avec précision, mais il a été trouvé avec précision, et il est important de comprendre que cela pourrait facilement être quelque chose qu'un profileur ne pourrait pas réellement trouver, comme quelque chose impliquant l'état des données, pas le compteur de programme.
Sur les serveurs Java, cela a toujours été une astuce de faire 2 à 3 rapidement Ctrl-Breakss dans une rangée et obtenez 2-3 vidages de threads de tous les threads en cours d'exécution. Le simple fait de regarder où se trouvent tous les threads "peut" déterminer très rapidement où se trouvent vos problèmes de performances.
Cette technique peut révéler plus de problèmes de performances en 2 minutes que toute autre technique que je connais.
Parce que parfois cela fonctionne, et parfois cela vous donne des réponses complètement fausses. Un profileur a de bien meilleurs résultats pour trouver la bonne réponse, et il y arrive généralement plus rapidement.
Faire cela manuellement ne peut pas vraiment être appelé "rapide" ou "efficace", mais il existe plusieurs outils de profilage qui le font automatiquement; également connu sous le nom de profilage statistique .
L'échantillonnage Callstack est une technique très utile pour le profilage, en particulier lorsque vous examinez une base de code volumineuse et compliquée qui pourrait passer son temps à un certain nombre d'endroits. Il a l'avantage de mesurer l'utilisation du processeur par le temps d'horloge murale, ce qui est important pour l'interactivité, et obtenir des piles d'appels avec chaque échantillon vous permet de voir pourquoi une fonction est appelée. Je l'utilise beaucoup, mais j'utilise des outils automatisés pour cela, tels que Luke Stackwalker et OProfile et diverses choses fournies par les fournisseurs de matériel.
La raison pour laquelle je préfère les outils automatisés à l'échantillonnage manuel pour le travail que je fais est puissance statistique . Saisir dix échantillons à la main est très bien lorsque vous avez une fonction occupant 40% du temps d'exécution, car en moyenne, vous en aurez quatre, et toujours au moins un. Mais vous avez besoin de plus d'échantillons lorsque vous avez un profil plat, avec des centaines de fonctions de feuille, aucune ne prenant plus de 1,5% du temps d'exécution.
Disons que vous avez un lac avec de nombreux types de poissons différents. Si 40% des poissons du lac sont des saumons (et 60% "tout le reste"), alors il vous suffit d'attraper dix poissons pour savoir qu'il y a beaucoup de saumons dans le lac. Mais si vous avez des centaines d'espèces de poissons différentes et que chaque espèce ne dépasse pas 1% individuellement, vous devrez attraper beaucoup plus de dix poissons pour pouvoir dire "ce lac est composé de 0,8% de saumon et de 0,6% de truite. . "
De même, dans les jeux sur lesquels je travaille, il existe plusieurs systèmes majeurs qui appellent chacun des dizaines de fonctions dans des centaines d'entités différentes, et tout cela se produit 60 fois par seconde. Le temps de certaines de ces fonctions se transforme en opérations courantes (comme malloc
), mais la plupart ne le font pas, et en tout cas il n'y a pas de feuille unique qui occupe plus de 1000 μs par image.
Je peux regarder les fonctions du tronc et voir, "nous passons 10% de notre temps en collision", mais ce n'est pas très utile: j'ai besoin pour savoir exactement où en collision, je sais donc quelles fonctions presser. Juste "faire moins de collisions" ne vous mène que jusqu'à présent, surtout quand cela signifie supprimer des fonctionnalités. Je préfère savoir "nous dépensons en moyenne 600 μs/trame sur les ratés de cache dans la phase étroite de l'octree parce que le missile magique se déplace si vite et touche beaucoup de cellules", car alors je peut trouver la solution exacte: soit un meilleur arbre, soit des missiles plus lents.
L'échantillonnage manuel serait bien s'il y avait une grosse masse de 20%, disons stricmp
, mais avec nos profils, ce n'est pas le cas. Au lieu de cela, j'ai des centaines de fonctions que je dois obtenir, disons de 0,6% de trame à 0,4% de trame. J'ai besoin de raser 10 μs toutes les 50 μs de fonction appelées 300 fois par seconde. Pour obtenir ce genre de précision, j'ai besoin de plus d'échantillons.
Mais au fond, ce que fait Luke Stackwalker est ce que vous décrivez: toutes les millisecondes environ, il arrête le programme et enregistre la pile d'appels (y compris les instructions précises et le numéro de ligne de la IP ). Certains programmes ont juste besoin de dizaines de milliers d'échantillons pour être utilement profilés.
(Nous en avons déjà parlé, bien sûr, mais je pensais que c'était un bon endroit pour résumer le débat.)
Je suis surpris par le ton religieux des deux côtés.
Le profilage est génial, et est certainement plus raffiné et précis lorsque vous pouvez le faire. Parfois, vous ne pouvez pas, et c'est bien d'avoir une sauvegarde fidèle. La technique de pause est comme le tournevis manuel que vous utilisez lorsque votre outil électrique est trop éloigné ou que les batteries sont épuisées.
Voici une courte histoire vraie. Une application (une sorte de tâche de traitement par lots) fonctionnait bien en production depuis six mois, du coup les opérateurs appellent les développeurs car ça va "trop lentement". Ils ne vont pas nous laisser attacher un profileur d'échantillonnage en production! Vous devez travailler avec les outils déjà installés. Sans arrêter le processus de production, en utilisant simplement Process Explorer , (que les opérateurs avaient déjà installé sur la machine), nous pouvions voir un instantané de la pile d'un thread. Vous pouvez regarder en haut de la pile, la fermer avec la touche Entrée et obtenir un autre instantané avec un autre clic de souris. Vous pouvez facilement obtenir un échantillon toutes les secondes environ.
Il ne faut pas longtemps pour voir si le haut de la pile se trouve le plus souvent dans la bibliothèque cliente de la base de données DLL (en attente sur la base de données), ou dans un autre système DLL (en attente d'une opération système), ou en fait dans une méthode de l'application elle-même. Dans ce cas, si je me souviens bien, nous avons rapidement remarqué que 8 fois sur 10, l'application était dans un système DLL appel de fichier lisant ou écrivant un fichier réseau. Il est certain que des "mises à niveau" récentes ont modifié les caractéristiques de performances d'un partage de fichiers. Sans une approche rapide et sale et (approuvée par l'administrateur système) pour voir ce que l'application faisait en production , nous aurions passé beaucoup plus de temps à essayer de mesurer le problème, qu'à le corriger.
D'un autre côté, lorsque les exigences de performances vont au-delà de "suffisamment bonnes" pour vraiment pousser l'enveloppe, un profileur devient essentiel pour que vous puissiez essayer de raser les cycles de tous vos dix ou vingt points chauds étroitement liés. Même si vous essayez simplement de respecter une exigence de performance modérée pendant un projet, lorsque vous pouvez aligner les bons outils pour vous aider à mesurer et à tester, et même les intégrer dans votre processus de test automatisé, cela peut être extrêmement utile.
Mais quand le courant est coupé (pour ainsi dire) et que les piles sont mortes, c'est bien de savoir utiliser ce tournevis manuel.
Donc, la réponse directe: sachez ce que vous pouvez apprendre de l'arrêt du programme, mais n'ayez pas peur des outils de précision non plus. Surtout, sachez quels emplois nécessitent quels outils.
Il y a une différence entre ce que les programmeurs font réellement et ce qu'ils recommandent aux autres.
Je connais beaucoup de programmeurs (moi y compris) qui utilisent réellement cette méthode. Cela aide seulement à trouver les problèmes de performances les plus évidents, mais c'est rapide et sale et cela fonctionne.
Mais je ne dirais pas vraiment aux autres programmeurs de le faire, car cela me prendrait trop de temps pour expliquer toutes les mises en garde. Il est beaucoup trop facile de tirer une conclusion inexacte sur la base de cette méthode, et il existe de nombreux domaines où cela ne fonctionne tout simplement pas. (par exemple, cette méthode ne révèle aucun code déclenché par une entrée utilisateur).
Donc, tout comme l'utilisation de détecteurs de mensonge en cour ou la déclaration "goto", nous ne vous recommandons tout simplement pas de le faire, même s'ils ont tous leur utilité.
Si nous prenons la question "Pourquoi n'est-il pas mieux connu?" alors la réponse va être subjective. Vraisemblablement, la raison pour laquelle elle n'est pas mieux connue est que le profilage fournit une solution à long terme plutôt qu'une solution actuelle au problème. Il n'est pas efficace pour les applications multithread et n'est pas efficace pour les applications comme les jeux qui passent une partie importante de son temps de rendu.
De plus, dans les applications à thread unique, si vous avez une méthode qui vous semble consommer le plus de temps d'exécution et que vous souhaitez réduire le temps d'exécution de toutes les autres méthodes, il sera plus difficile de déterminer les méthodes secondaires pour concentrer vos efforts. d'abord.
Votre processus de profilage est une méthode acceptable qui peut et qui fonctionne, mais le profilage vous fournit plus d'informations et a l'avantage de vous montrer des améliorations et des régressions de performances plus détaillées.
Si vous disposez d'un code bien instrumenté, vous pouvez examiner plus que la durée d'une méthode particulière; vous pouvez voir toutes les méthodes.
Avec profilage:
Vous pouvez ensuite réexécuter votre scénario après chaque modification pour déterminer le degré d'amélioration/de régression des performances.
Vous pouvez profiler le code sur différentes configurations matérielles pour déterminer si votre matériel de production sera suffisant.
Vous pouvez profiler le code sous des scénarios de test de charge et de stress pour déterminer comment le volume d'informations affecte les performances
Vous pouvez aider les développeurs juniors à visualiser plus facilement les impacts de leurs modifications sur votre code, car ils peuvent re-profiler le code en six mois pendant que vous êtes à la plage ou au pub, ou les deux. Pub de plage, ftw.
Le profilage a plus de poids parce que le code d'entreprise devrait toujours avoir un certain degré de profilage en raison des avantages qu'il donne à l'organisation d'une période de temps prolongée. Plus le code est important, plus vous faites de profilage et de tests.
Votre approche est valide et un autre élément est la boîte à outils du développeur. Il est juste contrebalancé par le profilage.
Les profileurs d'échantillonnage ne sont utiles que lorsque
Appuyer sur le bouton pause pendant l'exécution d'un programme en mode "débogage" peut ne pas fournir les bonnes données pour effectuer des optimisations de performances. Pour le dire franchement, c'est une forme grossière de profilage.
Si vous devez éviter d'utiliser un profileur, un meilleur pari consiste à utiliser un enregistreur, puis à appliquer un facteur de ralentissement pour "estimer" où se situe le vrai problème. Les profileurs, cependant, sont de meilleurs outils pour estimer.
La raison pour laquelle le fait d'appuyer sur le bouton pause en mode débogage peut ne pas donner une image réelle du comportement de l'application est que les débogueurs introduisent du code exécutable supplémentaire qui peut ralentir certaines parties de l'application. On peut se référer à article de blog de Mike Stall sur les raisons possibles du ralentissement des applications dans un environnement de débogage. Le post met en lumière certaines raisons comme trop de points d'arrêt, la création d'objets d'exception, du code non optimisé, etc. La partie sur le code non optimisé est importante - le mode "débogage" entraînera de nombreuses optimisations (généralement le code en ligne et ordering) étant jeté hors de la fenêtre, pour activer l'hôte de débogage (le processus exécutant votre code) et le IDE pour synchroniser l'exécution du code. Par conséquent, le fait d'appuyer plusieurs fois sur pause en mode "débogage" peut être Une mauvaise idée.
Ce doivent être des exemples triviaux avec lesquels vous travaillez pour obtenir des résultats utiles avec votre méthode. Je ne peux pas penser à un projet où le profilage était utile (par quelque méthode que ce soit) qui aurait obtenu des résultats décents avec votre méthode "rapide et efficace". Le temps qu'il faut pour démarrer et arrêter certaines applications remet déjà en question votre affirmation de "rapide".
Encore une fois, avec des programmes non triviaux, la méthode que vous préconisez est inutile.
EDIT: Concernant "pourquoi n'est-il pas mieux connu"?
D'après mon expérience, les revues de code évitent le code et les algorithmes de mauvaise qualité, et le profilage les trouverait également. Si vous souhaitez continuer avec votre méthode, c'est génial - mais je pense que pour la plupart des professionnels, c'est tellement loin sur la liste des choses à essayer qu'elle ne sera jamais renforcée par une bonne utilisation du temps.
Il semble être tout à fait inexact avec de petits ensembles d'échantillons et obtenir de grands ensembles d'échantillons prendrait beaucoup de temps qui aurait été mieux dépensé avec d'autres activités utiles.
Les instantanés de trace de pile vous permettent uniquement de voir les radiographies stroboscopiques de votre application. Vous pouvez avoir besoin de plus de connaissances accumulées qu'un profileur peut vous donner.
L'astuce consiste à bien connaître vos outils et à choisir le meilleur pour le travail à accomplir.
Que faire si le programme est en production et est utilisé en même temps par des clients ou des collègues payants. Un profileur vous permet d'observer sans interférer (autant, car bien sûr, il aura un petit coup aussi selon le principe de Heisenberg ).
Le profilage peut également vous donner des rapports précis beaucoup plus riches et plus détaillés. Ce sera plus rapide à long terme.
Parcourir le code est idéal pour voir les détails et les algorithmes de dépannage. C'est comme regarder un arbre de très près et suivre chaque veine d'écorce et de branche individuellement.
Le profilage vous permet de voir la situation dans son ensemble et d'identifier rapidement les problèmes - comme faire un pas en arrière et regarder toute la forêt et remarquer les arbres les plus hauts. En triant vos appels de fonction en fonction de la durée d'exécution, vous pouvez rapidement identifier les zones qui sont les points problématiques.
J'ai utilisé cette méthode pour Commodore 64 BASIC il y a de nombreuses années. Il est surprenant de voir à quel point cela fonctionne.
EDIT 2008/11/25: OK, la réponse de Vineet m'a finalement fait voir quel est le problème ici. Mieux vaut tard que jamais.
D'une manière ou d'une autre, l'idée s'est répandue dans le pays que les problèmes de performance sont trouvés en mesurant la performance. C'est confondre moyens et fins. D'une certaine manière, j'ai évité cela en exécutant des programmes entiers en une seule étape il y a longtemps. Je ne me suis pas réprimandé pour l'avoir ralenti à la vitesse humaine. J'essayais de voir si cela faisait des choses mauvaises ou inutiles. Voilà comment rendre le logiciel rapide - recherchez et supprimez les opérations inutiles.
Personne n'a la patience de faire un pas de nos jours, mais la meilleure chose à faire est de choisir un certain nombre de cycles au hasard et de demander quelles sont leurs raisons. (C'est ce que la pile d'appels peut souvent vous dire.) Si un bon pourcentage d'entre eux n'ont pas de bonnes raisons, vous pouvez faire quelque chose.
C'est plus difficile de nos jours, avec le threading et l'asynchronie, mais c'est comme ça I régler le logiciel - en trouvant des cycles inutiles. Pas en voyant à quelle vitesse c'est - je le fais à la fin.
Voici pourquoi l'échantillonnage de la pile d'appels ne peut pas donner une mauvaise réponse, et pourquoi pas beaucoup d'échantillons sont nécessaires.
Pendant l'intervalle d'intérêt, lorsque le programme prend plus de temps que vous ne le souhaiteriez, la pile d'appels existe en continu, même lorsque vous ne l'échantillonnez pas.
Si l'instruction apparaît sur M = 2 échantillons ou plus, sur N, son P(I) est approximativement M/N, et est certainement significatif.
La seule façon de ne pas voir l'instruction est de synchroniser comme par magie tous vos échantillons lorsque l'instruction n'est pas sur la pile d'appels. Le simple fait qu'il soit présent pendant une fraction du temps est ce qui l'expose à vos sondes.
Ainsi, le processus de réglage des performances est une simple question de choisir des instructions (principalement des instructions d'appel de fonction) qui lèvent la tête en retournant sur plusieurs échantillons de la pile d'appels. Ce sont les grands arbres de la forêt.
Notez que nous n'avons pas à nous soucier du graphe d'appel, ni de la durée des fonctions, ni du nombre d'appels, ni de la récursivité.
Je suis contre l'obscurcissement, pas contre les profileurs. Ils vous donnent beaucoup de statistiques, mais la plupart ne donnent pas P (I), et la plupart des utilisateurs ne réalisent pas que c'est ce qui compte.
Vous pouvez parler de forêts et d'arbres, mais pour tout problème de performances que vous pouvez résoudre en modifiant le code, vous devez modifier les instructions, en particulier les instructions avec un P (I) élevé. Vous devez donc savoir où ils se trouvent, de préférence sans jouer à Sherlock Holmes. L'échantillonnage de pile vous indique exactement où ils se trouvent.
Cette technique est plus difficile à utiliser dans les systèmes multi-threads, événementiels ou en production. C'est là que les profileurs, s'ils signalent P (I), pourraient vraiment aider.
Plus votre programme est volumineux, plus un profileur sera utile. Si vous avez besoin d'optimiser un programme qui contient des milliers de branches conditionnelles, un profileur peut être indispensable. Ajoutez votre plus grand échantillon de données de test et, une fois terminé, importez les données de profilage dans Excel. Ensuite, vous vérifiez vos hypothèses sur les points chauds probables par rapport aux données réelles. Il y a toujours des surprises.
Je l'ai généralement utilisé sur des programmes en temps réel qui dépassaient leur tranche de temps. Vous ne pouvez pas arrêter et redémarrer manuellement le code qui doit être exécuté 60 fois par seconde.
Je l'ai également utilisé pour localiser le goulot d'étranglement dans un compilateur que j'avais écrit. Vous ne voudriez pas essayer de casser un tel programme manuellement, parce que vous n'avez vraiment aucun moyen de savoir si vous cassez à l'endroit où se trouve le bottlenck, ou juste à l'endroit après le goulot d'étranglement lorsque le système d'exploitation est autorisé à revenir dans arrête ça. De plus, que se passe-t-il si le principal goulot d'étranglement est quelque chose que vous ne pouvez rien faire, mais que vous souhaitez vous débarrasser de tous les autres gros goulots d'étranglement du système? Comment prioriser les goulots d'étranglement à attaquer en premier, lorsque vous ne disposez pas de bonnes données sur leur emplacement tous, et quel est leur impact relatif chacun?