Où optimisez-vous?


9

Il y a deux domaines pour optimiser la vitesse:

  • Où passe le plus de temps
  • Le code qui est appelé le plus

Quel est le meilleur endroit pour commencer à optimiser?

Souvent, le code appelé le plus souvent a déjà des temps d'exécution faibles. Optimisez-vous les zones les plus lentes et les moins appelées ou passez-vous du temps à optimiser les zones les plus rapides et les plus utilisées?


Optimisez la zone de l'application qui sollicite le plus vos clients ou votre architecture, selon que vos clients ou vos serveurs se plaignent le plus fort.
Andy

C'est une équation de valeur - la réponse pourrait être soit. Lorsque vous n'avez pas de véritable analyse, vous allez avec votre instinct, basé sur le gain probable de vos meilleures idées.
Nicole

Ni. Recherchez le code qui se trouve sur la pile une grande partie du temps.
Mike Dunlavey

Réponses:


4

Vous devez ignorer les petites efficacités 95% du temps. D'abord, faites-le fonctionner correctement , puis analysez ...

Ton design.

Votre choix d'algorithmes de haut niveau peut avoir un impact énorme sur les performances globales de votre logiciel, au point qu'un choix apparemment banal peut faire la différence entre attendre 20 minutes pour que le programme démarre et avoir une interface utilisateur rapide et réactive.

Par exemple, dans un jeu 3D: si vous commencez avec une simple liste plate d'objets pour votre graphique de scène, vous verrez des performances extrêmement médiocres pour un nombre relativement petit d'objets; mais si vous implémentez une hiérarchie de volumes (comme un octree ou BVH) et supprimez des parties de l'arborescence pendant le dessin, vous verrez une augmentation considérable des performances.

Lorsque votre conception semble correcte, vous pouvez passer à ...

Logique de bas niveau.

Les algorithmes de niveau inférieur peuvent également avoir un impact significatif. Lorsque vous effectuez un traitement d'image, par exemple, si vous lisez l'image dans le mauvais ordre, vous subirez des ralentissements massifs lorsque vous rencontrerez des échecs de cache L2 constants; la réorganisation de vos opérations pourrait entraîner une multiplication par dix des performances.

À ce stade, profilez et trouvez l'endroit où la majorité du temps du programme est passé, et trouvez un moyen de l'éliminer.


Le programme sur lequel je travaille est correct. Nous voulons le rendre plus rapide si nous le pouvons, car il s'agit d'un service Web qui peut prendre plus de 30 à une minute pour fonctionner.
Michael K

1
@Michael: Dans ce cas, il est temps d'obtenir un outil de profilage pour analyser certaines exécutions de programme typiques et localiser les sections de code les plus lentes. Je recommanderais vraiment d'utiliser un outil pour cela. Vous pouvez faire un certain montant par intuition, mais parfois vous trouverez une surprise quand c'est une API qui appelle prend beaucoup de temps, plutôt que votre propre code. Dans ce cas, il est temps de regarder dans l'API et de voir quelles autres fonctions sont disponibles, ou si vous devez écrire votre propre fonction de remplacement ... Le porc de performance réel n'est pas toujours le porc de performance suspecté ...
FrustratedWithFormsDesigner

1
Nous avons un outil de profilage. Je l'apprends toujours et que faire des informations. C'est un sujet relativement nouveau pour moi, et très intéressant.
Michael K

@Michael: Bien! Vous êtes (espérons-le) sur la voie d'un réglage des performances réussi! :)
FrustratedWithFormsDesigner

+1: C'est ce que j'allais dire, mais de façon beaucoup plus éloquente.
Dominique McDonnell

3

Tout d'abord, exécutez un profileur pour savoir où votre code passe son temps.

Ensuite, regardez ces endroits pour voir ceux qui semblent faciles à optimiser.

Recherchez les correctifs les plus faciles qui obtiendront les gains les plus importants en premier (optez pour les fruits les plus bas). Ne vous inquiétez pas trop de son importance, précisément. Si c'est facile, corrigez-le. Cela va s'additionner. 25 correctifs faciles peuvent être plus rapides que 1 grand correctif et leurs effets cumulatifs peuvent être plus importants. Si c'est difficile, notez ou déposez un rapport de bogue pour pouvoir le prioriser plus tard. Ne vous inquiétez pas tellement de «gros» ou «petit» à ce stade - faites-le, jusqu'à ce que vous arriviez à des fonctions qui utilisent très peu de temps. Une fois que vous avez fait cela, vous devriez avoir une meilleure idée de laquelle des autres questions que vous avez découvertes pourrait obtenir les plus gros gains pour le moindre investissement en temps.

N'oubliez pas de suivre le profilage après vos correctifs comme une sorte de test de régression, pour vérifier que vos changements de performances ont eu les effets que vous espériez. N'oubliez pas non plus d'exécuter votre suite de régression pour vous assurer qu'aucune fonctionnalité n'a été interrompue. Parfois, de mauvaises performances indiquent des solutions de contournement, et essayer de corriger les performances interrompra la fonctionnalité.

Les petites fonctions qui ne peuvent pas être optimisées mais qui utilisent beaucoup de temps peuvent encore être des indications sur les endroits à optimiser. Pourquoi cette fonction est-elle tant appelée? Existe-t-il une fonction appelant cette petite fonction qui n'a pas besoin de l'utiliser autant? Le travail est-il dupliqué ou un travail inutile est-il effectué? Recherchez la pile pour les heures où elle est appelée jusqu'à ce que vous soyez sûr qu'elle devrait être appelée aussi souvent, et voyez si vous trouvez une fonction plus grande avec un algorithme inefficace.

Modifié pour ajouter: Étant donné que vous disposez de fonctionnalités spécifiques qui prennent beaucoup de temps, essayez de suivre les étapes ci-dessus avec uniquement cette fonction spécifique exécutée environ 10 fois.


2

C'est difficile à dire. Cela dépend vraiment de ce que fait le code. Exécutez un test de performances, obtenez un profil de performances et regardez et voyez combien de temps réel est passé dans divers domaines. Vos généralisations sont ... des généralisations et elles varient d'un projet à l'autre.

Par exemple, le code le plus appelé pourrait simplement se connecter à un fichier ou à une console. Il ne sert à rien d'optimiser que si c'est déjà une ou deux lignes de code qui ne peuvent pas être simplifiées, et il se pourrait que tout effort d'optimiser quelque chose comme ça ne vaille pas le coût de le coder. Le code le moins appelé pourrait être une requête de taille monstre utilisée dans une fonction horriblement complexe. La fonction ne peut être appelée que 100 fois sur une exécution entière (contre 10000 pour la simple instruction de journalisation), mais si cela prend 20 secondes pour chaque temps d'appel, c'est peut - être là que l' optimisation devrait commencer? Ou cela pourrait être l'inverse, avec la grande requête étant la plus appelée, et l'instruction de journalisation n'en a appelé qu'une pour 100 requêtes ...

Je ne m'inquiète généralement pas de ce genre de chose (jusqu'à ce que je doive faire un réglage des performances) à moins d'avoir une idée à l'avance de ce qui va se passer.


1

Eh bien, «nous» n'optimisons généralement pas tant qu'il n'y a pas un besoin évident d'optimisation lorsque quelque chose est trop lent.

Et lorsque ce besoin se manifeste, il comporte généralement de bons indices sur ce qui appelle exactement une optimisation.

La réponse est donc habituelle: "Cela dépend".


1

Vous devez utiliser un profileur sur une poignée d'exécutions typiques et regarder le temps total passé dans chaque partie du code, peu importe la fréquence ou la fréquence à laquelle vous y êtes arrivé. L'optimisation de ces pièces devrait toujours donner une augmentation de vitesse.

Selon le niveau de bas niveau de votre langage d'implémentation, vous devez également déterminer quelles parties provoquent la plupart des échecs de cache. La consolidation du code d'appel vous aidera ici.


1

Le problème est que l'expression "où passe le plus de temps" est ambiguë.

Si cela signifie "où le compteur de programmes se trouve le plus souvent", j'ai vu des programmes où le plus de temps a été consacré à des fonctions de comparaison de chaînes, d'allocation de mémoire et de bibliothèque mathématique. En d'autres termes, des fonctions que le programmeur ordinaire ne devrait jamais toucher.

Si cela signifie "où dans le code du programmeur sont exécutées des instructions qui consomment une grande partie du temps", c'est un concept plus utile.

Le problème avec le concept de "code qui est appelé le plus" est que le temps qu'il faut est le produit de la fréquence à laquelle il est appelé et du temps qu'il faut par appel (y compris les callees et les E / S). Étant donné que le temps qu'il faut peut varier sur plusieurs ordres de grandeur, le nombre de fois qu'il est appelé ne vous dit pas à quel point c'est un problème. La fonction A peut être appelée 10 fois et prendre 0,1 seconde, tandis que la fonction B peut être appelée 1000 fois et prendre une microseconde.

Une chose qui va vous dire où regarder est la suivante: Chaque fois qu'une ligne de code est à l' origine du temps à dépenser est sur la pile . Ainsi, par exemple, si une ligne de code est un point chaud, ou s'il s'agit d'un appel à une fonction de bibliothèque, ou s'il s'agit du 20e appel dans une arborescence d'appels à 30 niveaux, si elle est responsable de 20% du temps , alors c'est sur la pile 20% du temps. Les échantillons aléatoires de la pile auront chacun 20% de chances de s'afficher. De plus, si des échantillons peuvent être prélevés pendant les E / S, ils vous montreront ce qui explique les E / S, ce qui peut être tout autant ou plus inutile que les cycles CPU gaspillés.

Et cela est totalement indépendant du nombre de fois où il est invoqué.


Par «le programmeur ordinaire ne devrait jamais toucher», voulez-vous dire qu'il est peu probable qu'il touche? De plus, l'échantillonnage de la pile est-il une méthode de profilage pratique?
Michael K

@Michael: Oui, l'échantillonnage de la pile est une méthode sur laquelle les profileurs modernes sont basés, comme Zoom . De plus, le mode totalement manuel fonctionne étonnamment bien .
Mike Dunlavey

Très intéressant. J'ai des études à faire maintenant!
Michael K

@Michael: C'est comme le concept juridique de la responsabilité conjointe. À un moment donné, la responsabilité du PC dans une instruction est la responsabilité conjointe non seulement de cette instruction, mais de chaque appel au-dessus de celle-ci sur la pile. L'élimination de l'un d'eux l'empêcherait d'entrer dans cet état particulier.
Mike Dunlavey

0

Optimisez les endroits où vous passez le plus de temps, sauf s'il y a une raison particulière de ne pas le faire (c'est-à-dire que la plupart du temps est consacré à un traitement asynchrone que les humains ne se soucient pas vraiment de savoir s'il se termine en 5 minutes ou 10 minutes). Le code qui est appelé le plus, naturellement, aura tendance à accumuler une partie relativement importante du temps total écoulé simplement parce que même des temps d'exécution courts s'additionnent lorsque vous le faites des milliers de fois.


0

Vous devez travailler sur le code qui prend le plus de temps. L'amélioration du code qui ne représente que quelques pour cent du temps d'exécution ne peut que vous apporter une petite amélioration.

Avez-vous pris des mesures pour savoir quel code prend le plus de temps?


0

J'avais l'habitude de faire de l'analyse comparative et du marketing pour un fournisseur de superordinateurs, donc battre la concurrence dans le sens de la vitesse n'était pas la chose la plus importante, c'était la seule chose. Ce type de résultat exigeait que vous utilisiez une combinaison de bons algorithmes et d'une structure de données qui permettrait aux portions les plus gourmandes en CPU d'exécuter efficacement un pic. Cela signifiait que vous deviez avoir une assez bonne idée de ce que seraient les opérations les plus exigeantes en calcul, et quels types de structures de données leur permettraient de fonctionner plus rapidement. Il s'agissait ensuite de construire l'application autour de ces noyaux / structures de données optimisés.

Dans le sens plus général, le réglage après-coup typique. Vous profilez, regardez les points chauds et les points chauds que vous pensez pouvoir accélérer sont ceux sur lesquels vous travaillez. Mais rarement, cette approche vous donnera quelque chose de proche de la mise en œuvre la plus rapide possible.

Plus généralement, cependant (malgré les erreurs algorithmiques), pour les machines modernes, vous devez penser que les performances sont déterminées par trois choses: l'accès aux données, l'accès aux données et l'accès aux données! Découvrez la hiérarchie de la mémoire (registres, caches, TLB, pages, etc.) et concevez vos structures de données pour en faire le meilleur usage possible. En règle générale, cela signifie que vous souhaitez pouvoir exécuter des boucles dans un espace mémoire compact. Si, à la place, vous écrivez (ou recevez) une application, puis essayez de l'optimiser, vous êtes généralement confronté à des structures de données qui font un mauvais usage de la hiérarchie de la mémoire, et la modification des structures de données implique généralement un exercice de refactorisation majeur, vous êtes donc souvent coincé.


0

Si vous voulez un retour sur votre effort d'optimisation, vous devez regarder le code qui prend le plus de temps. Mon objectif général est quelque chose qui prend au moins 80% du temps. Je cible généralement un gain de performances de 10 fois. Cela nécessite parfois un changement majeur dans la conception de ce code. Ce type de changement vous permet d'obtenir quelque chose qui fonctionne environ quatre fois plus vite.

Mon meilleur gain de performances de tous les temps est de réduire un temps d'exécution de 3 jours à 9 minutes. Le code que j'ai optimisé est passé de 3 jours à 3 minutes. L'application a remplacé cette application réduite à 9 secondes, mais cela a nécessité un changement de langue et une réécriture complète.

L'optimisation d'une application déjà rapide peut être une folle course. Je visais toujours la zone qui prenait le plus de temps. Si vous pouvez prendre quelque chose en utilisant 10% du temps pour revenir instantanément, vous avez toujours besoin de 90% du temps. Vous frappez rapidement la règle des rendements décroissants.

Selon ce que vous optimisez, la règle s'applique toujours. Recherchez les principaux utilisateurs des ressources et optimisez-les. Si la ressource que vous optimisez est le goulot d'étranglement du système, vous constaterez peut-être que tout ce que vous faites est de changer le goulot d'étranglement en une autre ressource.


0

Généralement, ce seront des fonctions plus charnues dans la plupart des cas, pas les fonctions appelées le plus souvent un milliard de fois dans une boucle.

Lorsque vous effectuez un profilage basé sur des échantillons (avec un outil ou à la main), les plus grands hotspots vont souvent se trouver dans de minuscules appels feuillus qui font des choses simples, comme une fonction pour comparer deux entiers.

Cette fonction ne bénéficiera souvent pas d'une optimisation importante, voire inexistante. À tout le moins, ces points chauds granulaires sont rarement prioritaires. C'est la fonction appelant cette fonction feuille qui pourrait être le fauteur de troubles, ou la fonction appelant la fonction appelant la fonction, comme un algorithme de tri sous-optimal. Avec de bons outils, vous pouvez passer de l'appelé à l'appelant et voir qui passe le plus de temps à appeler l'appelé.

C'est souvent une erreur d'obséder sur les callees et de ne pas regarder les appelants sur le graphique des appels dans une session de profilage, sauf si vous faites les choses de manière très inefficace au niveau micro. Sinon, vous pourriez transpirer excessivement les petites choses et perdre de vue la grande image. Le simple fait d'avoir un profileur en main ne vous protège pas de l'obsession de choses triviales. Ce n'est qu'un premier pas dans la bonne direction.

Vous devez également vous assurer de profiler les opérations qui correspondent aux choses que les utilisateurs veulent réellement faire, sinon être totalement discipliné et scientifique dans vos mesures et vos références ne vaut rien car cela ne correspond pas à ce que les clients font avec le produit. J'ai eu un collègue une fois qui a réglé l'enfer d'un algorithme de subdivision pour subdiviser un cube en un milliard de facettes et il en était très fier .... sauf que les utilisateurs ne subdivisent pas de simples cubes à 6 polygones en un milliard facettes. Le tout a ralenti en rampant quand il a essayé de fonctionner sur un modèle de voiture de production avec plus de 100 000 polygones à subdiviser, auquel cas il ne pouvait même pas faire 2 ou 3 niveaux de subdivision sans ralentir en rampant. En termes simples, il a écrit du code qui était super optimisé pour des tailles d'entrée irréalistes de petite taille qui ne

Vous devez optimiser les cas d'utilisation réels alignés sur les intérêts de vos utilisateurs, sinon c'est pire qu'inutile, car toutes ces optimisations qui tendent à dégrader au moins quelque peu la maintenabilité du code ont peu d'avantages pour l'utilisateur et seulement tous les négatifs pour la base de code.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.