web-dev-qa-db-fra.com

Stratégies d'optimisation des performances de dernier recours

Il y a déjà beaucoup de questions sur les performances sur ce site, mais il me semble que presque toutes sont très spécifiques à un problème et assez étroites. Et presque tous répètent le conseil pour éviter une optimisation prématurée.

Assumons:

  • le code fonctionne déjà correctement
  • les algorithmes choisis sont déjà optimaux pour les circonstances du problème
  • le code a été mesuré et les routines incriminées ont été isolées
  • toutes les tentatives d'optimisation seront également mesurées pour s'assurer qu'elles n'aggravent pas les choses

Ce que je recherche ici, ce sont des stratégies et des astuces pour réduire jusqu’à quelques derniers pour cent dans un algorithme critique alors qu’il ne reste plus rien à faire, sauf ce que cela prend.

Idéalement, essayez de rendre les réponses indépendantes de la langue et indiquez les inconvénients des stratégies suggérées, le cas échéant.

J'ajouterai une réponse avec mes propres suggestions initiales et j'attendrai avec impatience toute autre chose à laquelle la communauté Stack Overflow pourra penser.

595
jerryjvl

OK, vous définissez le problème là où il semblerait qu’il n’y ait pas beaucoup de progrès à faire. C'est assez rare, d'après mon expérience. J'ai essayé d'expliquer cela dans un article de Dr. Dobbs paru en novembre 1993, en partant d'un programme non trivial conventionnellement bien conçu, sans aucun gaspillage évident, et en passant par une série d'optimisations jusqu'à ce que son temps total soit réduit de 48 secondes à 1,1 seconde, et la taille du code source a été réduite d'un facteur 4. Mon outil de diagnostic était-ce . La séquence de changements était la suivante:

  • Le premier problème constaté a été l'utilisation de groupes de listes (maintenant appelés "itérateurs" et "classes de conteneurs") représentant plus de la moitié du temps. Celles-ci ont été remplacées par un code assez simple, ramenant le temps à 20 secondes.

  • Maintenant, le preneur de temps le plus important est la construction de listes En pourcentage, ce n'était pas si gros avant, mais maintenant c'est parce que le plus gros problème a été éliminé. Je trouve un moyen de l'accélérer et le temps passe à 17 secondes.

  • Maintenant, il est plus difficile de trouver des coupables évidents, mais il y en a quelques-uns plus petits sur lesquels je peux faire quelque chose, et le temps passe à 13 secondes.

Maintenant, il me semble avoir heurté un mur. Les échantillons me disent exactement ce qu'il fait, mais je n'arrive pas à trouver quoi que ce soit que je puisse améliorer. Ensuite, je réfléchis à la conception de base du programme, à sa structure axée sur les transactions, et demande si toutes les recherches dans les listes qu'il effectue sont en réalité dictées par les exigences du problème.

Ensuite, je suis tombé sur une nouvelle conception, où le code du programme est réellement généré (via des macros de préprocesseur) à partir d’un ensemble de sources plus petit, et dans lequel le programme ne détecte pas constamment ce que le programmeur sait être assez prévisible. En d'autres termes, ne "interprétez" pas la séquence de choses à faire, "compilez-la".

  • Cette refonte est effectuée en réduisant le code source d'un facteur 4 et en réduisant le temps à 10 secondes.

Maintenant, parce que cela va très vite, il est difficile d’échantillonner, alors je lui donne 10 fois plus de travail, mais les temps suivants sont basés sur la charge de travail initiale.

  • Plus de diagnostic révèle qu'il passe du temps dans la gestion des files d'attente. In-lining réduit le temps à 7 secondes.

  • L’impression diagnostique que j’étais en train de faire est un grand preneur de temps. Rincer cela - 4 secondes.

  • Maintenant, les preneurs de temps les plus importants sont les appels vers malloc et gratuits . Recycler les objets - 2,6 secondes.

  • En continuant à échantillonner, je trouve toujours des opérations qui ne sont pas strictement nécessaires - 1,1 seconde.

Facteur total d'accélération: 43,6

Maintenant, il n'y a pas deux programmes identiques, mais dans les logiciels non-jouets, j'ai toujours vu une progression comme celle-ci. Vous obtenez d’abord les choses faciles, puis les plus difficiles, jusqu’à ce que vous obteniez des rendements décroissants. Ensuite, les connaissances que vous obtiendrez pourraient bien mener à une refonte, à une nouvelle série d’accélérations, jusqu’à ce que vous obteniez de nouveau des rendements décroissants. C’est à ce stade qu’il serait logique de se demander si ++i ou i++ ou for(;;) ou while(1) sont plus rapides: le type de questions que je vois si souvent sur Stack Débordement.

P.S. On peut se demander pourquoi je n'ai pas utilisé de profileur. La réponse est que presque chacun de ces "problèmes" était un site d’appel de fonction, qui empile les échantillons avec précision. Les profileurs, même aujourd'hui, commencent à peine à penser que les instructions et les instructions d'appel sont plus importantes à localiser et plus faciles à corriger que des fonctions entières.

En fait, j'ai construit un profileur pour faire cela, mais pour une vraie intimité avec ce que le code fait, rien ne peut remplacer le fait de bien comprendre le code. Ce n’est pas un problème si le nombre d’échantillons est petit, car aucun des problèmes découverts n’est tellement petit qu’il est facile de le rater.

AJOUTÉ: jerryjvl a demandé quelques exemples. Voici le premier problème. Il consiste en un petit nombre de lignes de code distinctes, prenant ensemble la moitié du temps:

 /* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)

Ceux-ci utilisaient la liste ILST (similaire à une classe de liste). Ils sont implémentés de la manière habituelle, avec "masquage d'informations", ce qui signifie que les utilisateurs de la classe n'étaient pas censés avoir à se soucier de la façon dont ils ont été implémentés. Lorsque ces lignes ont été écrites (sur environ 800 lignes de code), on n'a pas pensé à l'idée qu'elles pourraient constituer un "goulot d'étranglement" (je déteste ce mot). Ils sont simplement la façon recommandée de faire les choses. Il est facile de dire avec le recul que cela aurait dû être évité, mais dans mon expérience tout Les problèmes de performance sont comme ça. En général, il est bon d'essayer d'éviter de créer des problèmes de performances. Il est même préférable de rechercher et de réparer ceux qui sont créés, même s'ils "auraient dû être évités" (avec le recul). J'espère que cela donne un peu de la saveur.

Voici le deuxième problème, en deux lignes distinctes:

 /* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)

Ce sont des listes de construction en ajoutant des éléments à leurs extrémités. (Le correctif consistait à collecter les éléments dans des tableaux et à créer les listes en une fois.) Ce qui est intéressant, c’est que ces instructions ne coûtent que (c’est-à-dire étaient sur la pile d’appels) 3/48 de l’heure originale, elles ne sont donc pas fait un gros problème au début . Cependant, après avoir résolu le premier problème, ils coûtaient 3/20 du temps et étaient donc maintenant un "poisson plus gros". En général, c'est comme ça que ça se passe.

J'ajouterai que ce projet est issu d'un projet réel sur lequel j'ai contribué. Dans ce projet, les problèmes de performances étaient beaucoup plus dramatiques (tout comme les accélérations), par exemple appeler une routine d'accès à la base de données dans une boucle interne pour voir si une tâche était terminée.

RÉFÉRENCE AJOUTÉE: Le code source, original et modifié, peut être trouvé dans www.ddj.com , pour 1993, dans le fichier 9311.Zip, fichiers slug.asc et slug.Zip.

EDIT 2011/11/26: Il existe maintenant un projet SourceForge contenant le code source dans Visual C++ et une description détaillée de la manière dont il a été ajusté. Il ne traite que de la première moitié du scénario décrit ci-dessus, et il ne suit pas exactement la même séquence, mais accélère tout de même de 2 à 3 ordres de grandeur.

419
Mike Dunlavey

Suggestions:

  • Pré-calcul plutôt que recalculé: toute boucle ou appel répété contenant des calculs dont le nombre d'entrées est relativement limité, envisagez de lancer une recherche (tableau ou dictionnaire) contenant le résultat de ce calcul pour toutes les valeurs dans la plage d'entrées valide. Utilisez ensuite une simple recherche dans l’algorithme.
    Inconvénients : si peu de valeurs pré-calculées sont réellement utilisées, cela peut aggraver les choses, la recherche peut également prendre beaucoup Mémoire.
  • Ne pas utiliser les méthodes de bibliothèque: la plupart des bibliothèques doivent être écrites pour fonctionner correctement dans une large gamme de scénarios, et effectuer des contrôles nuls sur les paramètres, etc. En ré-implémentant une méthode, vous pourrez peut-être éliminez beaucoup de logique qui ne s'applique pas dans les circonstances exactes où vous l'utilisez.
    Inconvénients : écrire du code supplémentaire signifie plus de surface pour les bugs.
  • tilisez des méthodes de bibliothèque: pour me contredire, les bibliothèques de langue sont écrites par des personnes beaucoup plus intelligentes que vous ou moi; les chances sont qu'ils l'ont fait mieux et plus rapidement. Ne l'implémentez pas vous-même sauf si vous pouvez réellement l'accélérer (c.-à-d.: Toujours mesurer!)
  • Cheat: dans certains cas, même si un calcul exact peut exister pour votre problème, vous n'avez peut-être pas besoin de "exact", parfois une approximation peut être "assez bonne" et beaucoup plus rapide dans la transaction. Demandez-vous si la réponse est de 1%, est-ce vraiment important? 5%? même 10%?
    Inconvénients : Eh bien ... la réponse ne sera pas exacte.
186
jerryjvl

Lorsque vous ne pouvez plus améliorer les performances, voyez si vous pouvez améliorer les performances de perçues.

Vous ne pourrez peut-être pas rendre votre algorithme fooCalc plus rapide, mais il existe souvent des moyens de rendre votre application plus réactive pour l'utilisateur.

Quelques exemples:

  • anticiper ce que l'utilisateur va demander et commencer à travailler dessus avant
  • afficher les résultats au fur et à mesure qu'ils arrivent, au lieu de tout en même temps à la fin
  • Indicateur de progression précis

Cela ne rendra pas votre programme plus rapide, mais cela pourrait rendre vos utilisateurs plus heureux avec la vitesse que vous avez.

163
kenj0418

Je passe la plus grande partie de ma vie dans cet endroit. Les grandes lignes consistent à exécuter votre profileur et à le faire enregistrer:

  • Mises en cache. Le cache de données est la source n ° 1 de blocage dans la plupart des programmes. Améliorez le taux d'accès au cache en réorganisant les structures de données incriminées pour obtenir une meilleure localisation. regroupez les structures et les types numériques pour éliminer les octets gaspillés (et donc les récupérations de cache gaspillés); pré-lire les données autant que possible pour réduire les blocages.
  • Load-hit-stores. Les hypothèses du compilateur sur le crénelage du pointeur et les cas de transfert de données entre ensembles de registres déconnectés via la mémoire peuvent entraîner un certain comportement pathologique entraînant l'effacement de l'intégralité du pipeline de CPU lors d'un chargement. Trouvez des endroits où des flotteurs, des vecteurs et des ints sont moulés les uns aux autres et éliminez-les. Utilisez __restrict librement pour promettre au compilateur de créer des alias.
  • Opérations microcodées. La plupart des processeurs ont des opérations qui ne peuvent pas être mises en pipeline, mais exécutent à la place un minuscule sous-programme stocké dans la ROM. Des exemples sur le PowerPC sont multiplier, diviser et décalage par montant. Le problème est que tout le pipeline cesse de fonctionner pendant l'exécution de cette opération. Essayez d'éliminer l'utilisation de ces opérations ou au moins de les décomposer en leurs opérations en pipeline constituantes afin de pouvoir profiter de l'envoi superscalaire pour tout ce que fait le reste de votre programme.
  • Prédictions de branche. Ceux-ci aussi vident le pipeline. Recherchez les cas où le processeur passe beaucoup de temps à remplir le canal après une branche et utilisez l'indication de branche si elle est disponible pour le prédire correctement plus souvent. Ou mieux encore, remplacez les branches par des mouvements conditionnels autant que possible , en particulier après les opérations à virgule flottante, car leur canal est généralement plus profond et la lecture des indicateurs de condition après fcmp peut provoquer un décrochage.
  • Opérations en virgule flottante séquentielle. Faites ces SIMD.

Et une dernière chose que j'aime faire:

  • Configurez votre compilateur pour qu'il affiche les listes d'assembly et regardez ce qu'il émet pour les fonctions de hotspot dans votre code. Toutes ces optimisations astucieuses qu'un "bon compilateur devrait pouvoir faire pour vous automatiquement"? Les chances sont que votre compilateur actuel ne les fait pas. J'ai vu GCC émettre du code WTF.
137
Crashworks

Jetez plus de matériel à elle!

78
sisve

Plus de suggestions:

  • Evitez les E/S: Toutes les E/S (disque, réseau, ports, etc.) seront toujours beaucoup plus lentes que tout code effectuant des calculs, alors éliminez-les vous n'avez pas strictement besoin.

  • Déplacement d'E/S au départ: chargez toutes les données dont vous aurez besoin pour un calcul au préalable, de sorte que vous n'ayez plus d'attentes d'E/S répétées dans le cœur d'un algorithme critique (et peut-être en conséquence, plusieurs tentatives de recherche répétées sur le disque évitent la recherche lors du chargement de toutes les données en un seul passage).

  • Retard I/O: N'écrivez pas vos résultats tant que le calcul n'est pas terminé, stockez-les dans une structure de données, puis extrayez-les en une seule fois à la fin du travail acharné.

  • Threaded I/O: Pour ceux qui ont le courage de casser la tête, combinez 'I/O up-front' ou 'Delay I/O' avec le calcul réel en déplaçant le chargement dans un thread parallèle. chargez plus de données, vous pouvez travailler sur un calcul avec les données que vous avez déjà, ou pendant que vous calculez le prochain lot de données, vous pouvez écrire simultanément les résultats du dernier lot.

58
jerryjvl

Étant donné que de nombreux problèmes de performances impliquent des problèmes de base de données, je vais vous donner quelques éléments à prendre en compte lors du réglage des requêtes et des procédures stockées.

Évitez les curseurs dans la plupart des bases de données. Évitez de faire de la boucle aussi. La plupart du temps, l’accès aux données doit être défini et non pas traiter par enregistrement. Cela inclut la non-réutilisation d'une procédure stockée d'enregistrement unique lorsque vous souhaitez insérer 1 000 000 enregistrements à la fois.

N'utilisez jamais select *, ne retournez que les champs dont vous avez réellement besoin. Cela est particulièrement vrai s'il y a des jointures car les champs de jointure seront répétés, ce qui entraînerait une charge inutile sur le serveur et le réseau.

Évitez d'utiliser des sous-requêtes corrélées. Utilisez des jointures (y compris des jointures dans les tables dérivées, si possible) (je sais que cela est vrai pour Microsoft SQL Server, mais testez les conseils lorsque vous utilisez un backend différent).

Index, index, index. Et obtenez ces statistiques mises à jour si applicable à votre base de données.

Faites la requête sargable . Cela signifie éviter les choses qui rendent impossible l'utilisation des index, telles que l'utilisation d'un caractère générique dans le premier caractère d'une clause similaire ou d'une fonction dans la jointure ou comme partie gauche d'une instruction where.

Utilisez les types de données corrects. Il est plus rapide de calculer un calcul de date sur un champ de date que d'essayer de convertir un type de données chaîne en un type de date, puis d'effectuer le calcul.

Ne mettez jamais une boucle d'aucune sorte dans une gâchette!

La plupart des bases de données disposent d'un moyen de vérifier comment l'exécution de la requête sera effectuée. Dans Microsoft SQL Server, cela s'appelle un plan d'exécution. Vérifiez-les d'abord pour voir où se situent les problèmes.

Déterminez la fréquence d'exécution de la requête et le temps nécessaire à son exécution pour déterminer ce qui doit être optimisé. Parfois, vous pouvez obtenir plus de performances d'un simple Tweak à une requête exécutée des millions de fois par jour que d'une suppression d'une requête longue exécution qui ne s'exécute qu'une fois par mois.

Utilisez une sorte d’outil de profilage pour savoir ce qui est réellement envoyé vers et à partir de la base de données. Je me souviens d'une fois où nous ne pouvions pas comprendre pourquoi la page était si lente à charger alors que la procédure stockée était rapide et que, grâce au profilage, la page Web demandait la requête plusieurs fois au lieu d'une fois.

Le profileur vous aidera également à trouver qui bloque qui. Certaines requêtes qui s'exécutent rapidement lorsqu'elles s'exécutent seules peuvent devenir très lentes à cause des verrous d'autres requêtes.

47
HLGEM

Le facteur limitant le plus important aujourd'hui est le bande mémoire limitée. Les multicores ne font qu'aggraver la situation, car la bande passante est partagée entre les cœurs. En outre, la zone de puce limitée consacrée à la mise en place de caches est également divisée entre les cœurs et les threads, aggravant encore ce problème. Enfin, la signalisation entre puces nécessaire pour garder les différentes caches cohérentes augmente également avec l’augmentation du nombre de cœurs. Cela ajoute aussi une pénalité.

Ce sont les effets que vous devez gérer. Parfois, grâce à la micro gestion de votre code, mais parfois grâce à une réflexion approfondie et à une refonte.

Beaucoup de commentaires mentionnent déjà le code convivial du cache. Il y a au moins deux saveurs distinctes:

  • Évitez les latences d'extraction de mémoire.
  • Abaissez la pression du bus mémoire (bande passante).

Le premier problème concerne plus particulièrement la régularisation de vos schémas d’accès aux données, ce qui permet au préfetcher matériel de fonctionner efficacement. Évitez l’allocation dynamique de mémoire qui répartit vos objets de données en mémoire. Utilisez des conteneurs linéaires au lieu de listes chaînées, de hachages et d’arbres.

Le deuxième problème concerne l'amélioration de la réutilisation des données. Modifiez vos algorithmes pour travailler sur des sous-ensembles de vos données qui tiennent dans le cache disponible, et réutilisez ces données autant que possible tant qu'elles sont encore dans le cache.

Le fait de compresser les données plus étroitement et d’assurer que vous utilisez toutes les données dans les lignes de cache des boucles dynamiques vous aidera à éviter ces autres effets et permettra d’ajouter plus de données utiles dans la cache.

29
Mats N
  • Sur quel matériel utilisez-vous? Pouvez-vous utiliser des optimisations spécifiques à la plateforme (comme la vectorisation)?
  • Pouvez-vous obtenir un meilleur compilateur? Par exemple. passer de GCC à Intel?
  • Pouvez-vous faire fonctionner votre algorithme en parallèle?
  • Pouvez-vous réduire les erreurs de cache en réorganisant les données?
  • Pouvez-vous désactiver les assertions?
  • Micro-optimisez pour votre compilateur et votre plate-forme. Dans le style de "à un if/else, mettez en premier la déclaration la plus courante"
25
Johan Kotlinski

Vous devriez probablement prendre en compte la "perspective Google", c’est-à-dire déterminer comment votre application peut devenir largement parallélisée et concurrente, ce qui impliquera inévitablement aussi à un moment donné de réfléchir à la répartition de votre application sur différentes machines et réseaux, de manière à ce qu'elle puisse évoluer idéalement de manière presque linéaire. avec le matériel que vous lancez.

D'autre part, les employés de Google sont également réputés pour leur main-d'œuvre et leurs ressources qui leur permettent de résoudre certains problèmes liés aux projets, outils et infrastructures qu'ils utilisent, tels que par exemple optimisation du programme complet pour gcc en faisant appel à une équipe d'ingénieurs dédiés au piratage des composants internes de GCC afin de le préparer aux scénarios de cas d'utilisation typiques de Google.

De même, profiler une application ne signifie plus simplement profiler le code du programme, mais également l'ensemble de ses systèmes et infrastructures environnants (réseaux, commutateurs, serveurs, matrices RAID) afin d'identifier les redondances et le potentiel d'optimisation du point de vue du système.

16
none

Bien que j'aime la réponse de Mike Dunlavey, en fait c'est une excellente réponse avec un exemple à l'appui, je pense qu'elle pourrait être exprimée très simplement ainsi:

Découvrez d'abord ce qui prend le plus de temps, et comprenez pourquoi.

C’est le processus d’identification des facteurs critiques qui vous aide à comprendre où vous devez affiner votre algorithme. C’est la seule réponse agnostique à tous les langages que je puisse trouver à un problème déjà supposé être pleinement optimisé. En supposant également que vous souhaitiez être indépendant de l'architecture dans votre quête de rapidité.

Ainsi, bien que l'algorithme puisse être optimisé, sa mise en œuvre peut ne pas l'être. L'identification vous permet de savoir quelle partie est laquelle: algorithme ou implémentation. Donc, quel que soit le moment le plus favorable, votre candidat est le premier à passer en revue. Mais puisque vous dites vouloir éliminer les derniers%, vous pouvez également examiner les parties les plus petites, les parties que vous n’avez pas examinées aussi attentivement au début.

Enfin, quelques essais et erreurs avec des chiffres de performances sur différentes manières de mettre en œuvre la même solution, ou des algorithmes potentiellement différents, peuvent apporter des informations permettant d'identifier les perdants et les économiseurs de temps.

HPH, asoudmove.

16
asoundmove
  • Routines en ligne (élimine les appels/retours et les poussées de paramètres)
  • Essayez d'éliminer les tests/commutateurs avec des consultations de table (si elles sont plus rapides)
  • Dérouler les boucles (le périphérique de Duff) au point qu'elles entrent dans le cache du processeur
  • Localiser les accès mémoire pour ne pas faire exploser votre cache
  • Localiser les calculs associés si l'optimiseur ne le fait pas déjà
  • Éliminez les invariants de boucle si l'optimiseur ne le fait pas déjà
15
plinth

Diviser pour régner

Si le jeu de données en cours de traitement est trop volumineux, passez en boucle sur des fragments de celui-ci. Si votre code est correct, sa mise en oeuvre devrait être facile. Si vous avez un programme monolithique, vous savez maintenant mieux.

12
MPelletier
  • Lorsque vous parvenez à utiliser des algorithmes efficaces, vous vous demandez ce dont vous avez besoin davantage en termes de vitesse ou de mémoire . Utilisez la mise en cache pour "payer" en mémoire plus rapidement ou utilisez des calculs pour réduire l'encombrement de la mémoire.
  • Si possible (et plus rentable) , jetez le matériel au problème - un processeur plus rapide, plus de mémoire ou plus de HD pourraient résoudre le problème plus rapidement que d'essayer de le coder.
  • Utilisez la parallélisation si possible - exécutez une partie du code sur plusieurs threads.
  • Utilisez le bon outil pour le travail . Certains langages de programmation créent un code plus efficace, l'utilisation de code géré (par exemple, Java/.NET) accélère le développement, mais les langages de programmation natifs créent un code plus rapide.
  • Optimisation micro . Seuls étaient applicables, vous pouvez utiliser Assembly optimisé pour accélérer les petits morceaux de code; utiliser les optimisations SSE/vectorielles aux bons endroits peut considérablement améliorer les performances.
12
Dror Helper

Tout d’abord, comme mentionné dans plusieurs réponses précédentes, découvrez ce qui gâche votre performance: mémoire, processeur, réseau, base de données ou autre. En fonction de cela ...

  • ... si c'est de la mémoire - retrouvez l'un des livres écrits il y a longtemps par Knuth, l'un des ouvrages de la série "The Art of Computer Programming". Il s’agit très probablement d’une question de tri et de recherche - si ma mémoire est fausse, vous devrez trouver dans quel sujet il parle de la façon de traiter le stockage lent des données sur bande. Transformez mentalement sa paire mémoire/bande en votre paire de mémoire cache/mémoire principale (ou en paire de mémoire cache L1/L2) respectivement. Étudiez tous les trucs qu’il décrit - si vous ne trouvez pas quelque chose qui résout votre problème, faites alors appel à un informaticien professionnel pour mener une recherche professionnelle. Si votre problème de mémoire est dû au hasard avec FFT (le cache manque aux index à inversion de bits lorsque vous utilisez des papillons à la base 2), n'engagez pas de scientifique, mais optimisez manuellement les passes un par un jusqu'à ce que vous gagniez ou obteniez à l'impasse. Vous avez mentionné presser jusqu'au dernier pour cent pas vrai? Si c'est pe vous gagnerez probablement.

  • ... si c'est un processeur - passez en langage d'assemblage. Spécification du processeur d'étude - ce qui prend des ticks, VLIW, SIMD. Les appels de fonction sont très probablement des mangeurs de tiques remplaçables. Apprenez les transformations de boucle - pipeline, dérouler. Les multiplications et les divisions peuvent être remplaçables/interpolées avec des décalages de bits (les multiplications par de petits entiers peuvent être remplacées par des ajouts). Essayez des astuces avec des données plus courtes - si vous êtes chanceux, une instruction avec 64 bits peut être remplacée par deux sur 32 ou même 4 sur 16 ou 8 sur 8 bits. Essayez aussi plus long données - par exemple, vos calculs de float peuvent s'avérer plus lents que ceux en double chez un processeur donné. Si vous avez des éléments trigonométriques, combattez-les avec des tables pré-calculées. N'oubliez pas non plus que les sinus de faible valeur peuvent être remplacés par cette valeur si la perte de précision se situe dans les limites autorisées.

  • ... s'il s'agit d'un réseau, pensez à compresser les données que vous transmettez. Remplacer le transfert XML par binaire. Protocoles d'étude. Essayez UDP au lieu de TCP si vous pouvez gérer les pertes de données.

  • ... si c'est une base de données, allez sur n'importe quel forum et demandez conseil. Grille de données en mémoire, optimisation du plan de requête, etc., etc.

HTH :)

11
gnat

Caching! Un moyen peu coûteux (en termes d'effort de la part des programmeurs) d'accélérer pratiquement tout ce qui est plus rapide consiste à ajouter une couche d'abstraction de mise en cache à toute zone de transfert de données de votre programme. Que ce soit I/O ou simplement passer/créer des objets ou des structures. Il est souvent facile d’ajouter des caches aux classes d’usine et aux lecteurs/écrivains.

Parfois, le cache ne vous apportera pas grand-chose, mais c’est une méthode simple d’ajouter du cache puis de le désactiver là où cela n’aide en rien. J'ai souvent constaté que cela permettait d'obtenir des performances énormes sans avoir à analyser le code par micro-analyse.

9
Killroy

Je pense que cela a déjà été dit d'une manière différente. Toutefois, lorsque vous utilisez un algorithme gourmand en ressources processeur, vous devez tout simplifier au sein de la boucle la plus interne au détriment de tout le reste.

Cela peut sembler évident à certains, mais c'est une chose sur laquelle j'essaie de me concentrer, peu importe la langue dans laquelle je travaille. Par exemple, si vous avez affaire à des boucles imbriquées et que vous trouvez une opportunité de réduire un code, vous pouvez dans certains cas accélérer considérablement votre code. Comme autre exemple, il y a quelques petites choses à penser, comme travailler avec des entiers plutôt que des variables à virgule flottante chaque fois que vous le pouvez, et utiliser la multiplication plutôt que la division chaque fois que vous le pouvez. Encore une fois, ce sont des choses qui devraient être considérées pour votre boucle la plus intérieure.

Parfois, il peut être avantageux d’effectuer vos opérations mathématiques sur un nombre entier à l’intérieur de la boucle interne, puis de le réduire à une variable à virgule flottante avec laquelle vous pourrez travailler par la suite. C'est un exemple de perte de vitesse dans une section pour améliorer la vitesse dans une autre, mais dans certains cas, le résultat peut en valoir la peine.

8
Steve Wortham

J'ai consacré du temps à l'optimisation des systèmes d'entreprise client/serveur fonctionnant sur des réseaux à faible bande passante et à latence longue (satellite, distant, offshore, etc.) et j'ai pu améliorer considérablement les performances avec un processus relativement reproductible.

  • Measure: Commencez par comprendre la capacité et la topologie sous-jacentes du réseau. Parlez aux personnes concernées du réseau dans le réseau et utilisez des outils de base tels que ping et traceroute pour établir (au minimum) la latence du réseau à partir de chaque site client, au cours de périodes d'exploitation typiques. Ensuite, prenez des mesures de temps précises de fonctions spécifiques de l’utilisateur final qui affichent les symptômes problématiques. Notez toutes ces mesures, ainsi que leurs emplacements, dates et heures. Envisagez de créer une fonctionnalité de "test de performances réseau" pour les utilisateurs finaux dans votre application client, permettant ainsi aux utilisateurs expérimentés de participer au processus d'amélioration. Les responsabiliser de cette manière peut avoir un énorme impact psychologique lorsque les utilisateurs sont frustrés par un système peu performant.

  • Analyse: Utiliser l’une ou l’autre des méthodes de journalisation disponibles pour déterminer avec précision les données transmises et reçues pendant l’exécution des opérations concernées. Idéalement, votre application peut capturer les données transmises et reçues par le client et le serveur. Si ceux-ci incluent également des horodatages, c'est encore mieux. Si la journalisation est insuffisante (par exemple, système fermé ou incapacité de déployer des modifications dans un environnement de production), utilisez un renifleur de réseau et assurez-vous de bien comprendre ce qui se passe au niveau du réseau.

  • Cache: Recherchez les cas où des données statiques ou peu modifiées sont transmises de manière répétée et envisagez une stratégie de mise en cache appropriée. Des exemples typiques incluent les valeurs de "liste de sélection" ou d'autres "entités de référence", qui peuvent être étonnamment grandes dans certaines applications métier. Dans de nombreux cas, les utilisateurs peuvent accepter le redémarrage ou l'actualisation de l'application pour mettre à jour des données rarement mises à jour, en particulier si cela peut réduire considérablement le temps d'affichage des éléments de l'interface utilisateur couramment utilisés. Assurez-vous de bien comprendre le comportement réel des éléments de mise en cache déjà déployés - de nombreuses méthodes de mise en cache courantes (par exemple, HTTP ETag) nécessitent encore un aller-retour réseau pour assurer la cohérence. Si la latence du réseau est coûteuse, vous pouvez peut-être l'éviter complètement. une approche de mise en cache différente.

  • Parallelise: Recherchez les transactions séquentielles qui n'ont pas logiquement besoin d'être émises séquentiellement, et retravaillez le système pour les émettre en parallèle. J'ai traité un cas dans lequel une demande de bout en bout présentait un délai réseau inhérent de ~ 2 secondes, ce qui n'était pas un problème pour une transaction unique, mais lorsque 6 allers-retours séquentiels étaient nécessaires avant que l'utilisateur ne reprenne le contrôle de l'application cliente. , c’est devenu une énorme source de frustration. En découvrant que ces transactions étaient en réalité indépendantes, elles ont pu être exécutées en parallèle, ce qui a permis de réduire le délai de l’utilisateur final à un coût très proche du coût d’un aller-retour.

  • Combine: Lorsque des requêtes séquentielles doivent être exécutées de manière séquentielle, recherchez les possibilités de les combiner en une seule requête plus complète. Des exemples typiques incluent la création de nouvelles entités, suivies de demandes visant à associer ces entités à d'autres entités existantes.

  • Compress: Recherchez les possibilités d’utiliser la compression de la charge utile en remplaçant une forme textuelle par une forme binaire ou en utilisant la technologie de compression réelle. De nombreuses piles de technologies modernes (c’est-à-dire au cours d’une décennie) supportent cela de manière presque transparente, alors assurez-vous qu’elles sont configurées. J'ai souvent été surpris par l'impact significatif de la compression, où il semblait clair que le problème était fondamentalement latence plutôt que bande passante, découvrant après le fait que cela permettait à la transaction de s'intégrer dans un seul paquet ou d'éviter la perte de paquets et donc d'avoir une taille démesurée. impact sur les performances.

  • Repeat: Revenez au début et re-mesurez vos opérations (aux mêmes endroits et aux mêmes heures) avec les améliorations en place, enregistrez et rapportez vos résultats. Comme pour toute optimisation, certains problèmes ont peut-être été résolus, exposant d’autres qui dominent maintenant.

Dans les étapes ci-dessus, je me concentre sur le processus d'optimisation lié aux applications, mais vous devez bien sûr vous assurer que le réseau sous-jacent est lui-même configuré de la manière la plus efficace pour prendre également en charge votre application. Engagez les spécialistes réseau de votre entreprise et déterminez s'ils sont capables d'appliquer des améliorations de capacité, de la qualité de service, de la compression réseau ou d'autres techniques pour résoudre le problème. Habituellement, ils ne comprendront pas les besoins de votre application. Il est donc important que vous soyez outillé (après l'étape d'analyse) pour en discuter avec eux, et aussi pour l'analyse de rentabilisation des coûts que vous leur demanderez d'assumer. . J'ai rencontré des cas où une configuration de réseau erronée entraînait la transmission des données d'applications via une liaison satellite lente plutôt que par une liaison terrestre, simplement parce qu'elle utilisait un port TCP qui n'était pas "bien connu" par l'éditeur. spécialistes du réseautage; Évidemment, la résolution d'un problème comme celui-ci peut avoir un impact considérable sur les performances, aucun code logiciel ni modification de configuration n'étant nécessaire.

8
Pat

Pas aussi en profondeur ou complexe que les réponses précédentes, mais voici: (ce sont des niveaux débutant/intermédiaire)

  • évident: sec
  • exécuter des boucles en arrière de sorte que vous comparez toujours à 0 plutôt qu'à une variable
  • utiliser des opérateurs au niveau des bits chaque fois que vous pouvez
  • diviser le code répétitif en modules/fonctions
  • objets en cache
  • les variables locales ont un léger avantage de performance
  • limiter autant que possible la manipulation des chaînes
7
Aaron

Très difficile de donner une réponse générique à cette question. Cela dépend vraiment de votre domaine de problèmes et de votre mise en œuvre technique. Une technique générale assez neutre du point de vue du langage: identifiez les zones sensibles de code qui ne peuvent pas être éliminées et optimisez manuellement le code de l'assembleur.

7
dschwarz

Last%% est très dépendant du processeur et de l'application ....

  • les architectures de cache diffèrent, certaines puces ont sur la puce RAM vous pouvez mapper directement, les ARM ont (parfois) une unité vectorielle, SH4 est un opcode de matrice utile. Y at-il un GPU - peut-être un shader est la voie à suivre. Les TMS32 sont très sensibles aux branches dans les boucles (séparez les boucles et déplacez les conditions à l'extérieur si possible).

La liste continue ... Mais ce genre de choses est vraiment le dernier recours ...

Générez pour x86 et exécutez Valgrind /Cachegrind contre le code pour obtenir un profil de performances approprié. Ou --- CCStudio de Texas Instruments a un profileur adorable. Ensuite, vous saurez vraiment où vous concentrer.

7
Cwaig

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

Pour tous les projets non hors connexion, tout en ayant les meilleurs logiciels et le meilleur matériel, si votre capacité de traitement est faible, cette ligne mince va comprimer les données et vous donner des retards, même en millisecondes ... mais si vous parlez des dernières baisses , cela fait quelques gouttes gagnées, 24 heures sur 24, 7 jours sur 7, pour tout paquet envoyé ou reçu.

7
Sam

Ajout de cette réponse puisque je ne l'ai pas vu inclus dans tous les autres.

Minimiser la conversion implicite entre les types et le signe:

Cela s'applique au moins au C/C++, même si vous êtes déjà pensez vous êtes libre de conversions - il est parfois utile de tester l'ajout des avertissements du compilateur autour des fonctions qui exigent des performances, en particulier la surveillance des conversions au sein des boucles .

GCC spesific: Vous pouvez le tester en ajoutant des pragmas verbeux autour de votre code,

#ifdef __GNUC__
#  pragma GCC diagnostic Push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

J'ai vu des cas d'accélération de quelques pour cent en réduisant les conversions générées par des avertissements comme celui-ci.

Dans certains cas, j'ai un en-tête avec des avertissements stricts que je garde inclus pour empêcher les conversions accidentelles. Cependant, c'est un compromis, car vous pouvez finir par ajouter de nombreux conversions pour calmer les conversions intentionnelles, ce qui risque de rendre le code plus encombré pour un minimum. des gains.

5
ideasman42

Impossible à dire. Cela dépend de ce que le code ressemble. Si nous pouvons supposer que le code existe déjà, alors nous pouvons simplement l'examiner et déterminer à partir de cela comment l'optimiser.

Essayez de supprimer les longues chaînes de dépendances pour obtenir un meilleur parallélisme au niveau des instructions. Préférez les déplacements conditionnels aux branches lorsque cela est possible. Exploiter les instructions SIMD lorsque cela est possible.

Comprenez ce que fait votre code et comprenez le matériel sur lequel il tourne. Il devient alors assez simple de déterminer ce que vous devez faire pour améliorer les performances de votre code. C'est vraiment le seul conseil vraiment général auquel je puisse penser.

Et bien, et "Affichez le code sur SO et demandez des conseils d'optimisation pour cet élément de code spécifique".

5
jalf

Voici quelques techniques d'optimisation rapides et sales que j'utilise. Je considère que ceci est une optimisation de "premier passage".

Découvrez où le temps est passé Découvrez exactement ce qui prend le temps. Est-ce le fichier IO? Est-ce le temps CPU? Est-ce le réseau? Est-ce la base de données? Il est inutile d'optimiser pour IO si ce n'est pas le goulot d'étranglement.

Connaissez votre environnement Le choix de l’optimisation dépend généralement de l’environnement de développement. Dans VB6, par exemple, le passage par référence est plus lent que le passage par valeur, mais en C et C++, par référence est beaucoup plus rapide. En C, il est raisonnable d'essayer quelque chose et de faire autre chose si un code de retour indique un échec, tandis que dans Dot Net, la détection des exceptions est beaucoup plus lente que la recherche d'une condition valide avant d'essayer.

Index Construisez des index sur des champs de base de données fréquemment interrogés. Vous pouvez presque toujours échanger de l'espace pour la vitesse.

Éviter les recherches À l'intérieur de la boucle pour être optimisé, j'évite de faire des recherches. Recherchez le décalage et/ou l'index en dehors de la boucle et réutilisez les données à l'intérieur.

Minimize IO essayez de concevoir de manière à réduire le nombre de fois que vous devez lire ou écrire, en particulier via une connexion en réseau.

Réduire les abstractions Plus le code doit traiter de couches d'abstraction, plus il est lent. À l’intérieur de la boucle critique, réduisez les abstractions (par exemple, révélez des méthodes de niveau inférieur qui évitent le code supplémentaire)

Spawn Threads pour les projets avec une interface utilisateur, générer un nouveau thread pour effectuer des tâches plus lentes rend l'application plus sensible , bien que n'est pas.

Pre-process Vous pouvez généralement échanger de l'espace pour la vitesse. S'il y a des calculs ou d'autres opérations intenses, voyez si vous pouvez précalculer certaines informations avant que vous ne soyez dans la boucle critique.

5
Andrew Neely

Si un meilleur matériel est une option, alors optez pour cela. Autrement

  • Vérifiez que vous utilisez les meilleures options du compilateur et de l'éditeur de liens.
  • Si la routine du point chaud dans une bibliothèque différente correspond à un appelant fréquent, envisagez de la déplacer ou de la cloner dans le module appelants. Élimine une partie de la surcharge de l'appel et peut améliorer les accès au cache (voir comment AIX lie strcpy () de manière statique en objets partagés liés séparément). Bien entendu, cela pourrait également réduire les occurrences dans la mémoire cache, ce qui explique pourquoi une mesure.
  • Vérifiez s'il existe une possibilité d'utiliser une version spécialisée de la routine de point d'accès. Inconvénient est plus d'une version à maintenir.
  • Regarde l'assembleur. Si vous pensez que cela pourrait être mieux, demandez-vous pourquoi le compilateur n'a pas compris cela et comment vous pourriez aider le compilateur.
  • Considérez: utilisez-vous vraiment le meilleur algorithme? Est-ce le meilleur algorithme pour votre taille d'entrée?
5
mealnor

Le moyen de Google est une option "Cachez-le. Dans la mesure du possible, ne touchez pas le disque"

5
asyncwait

Réduire les tailles variables (dans les systèmes embarqués)

Si la taille de votre variable est supérieure à la taille de Word dans une architecture spécifique, cela peut avoir un effet significatif sur la taille et la vitesse du code. Par exemple, si vous avez un système 16 bits et utilisez une variable long int très souvent, et réalisez plus tard qu'il ne peut jamais sortir de la plage (−32,768 ... 32,767), envisagez de le réduire à short int..

D'après mon expérience personnelle, si un programme est prêt ou presque, mais que nous réalisons qu'il nécessite environ 110% ou 120% de la mémoire du programme du matériel cible, une normalisation rapide des variables résout généralement le problème plus souvent.

À ce stade, l’optimisation des algorithmes ou de parties du code lui-même peut devenir frustrante et inutile:

  • réorganisez toute la structure et le programme ne fonctionnera plus comme prévu ou du moins introduisez-vous beaucoup de bogues.
  • faites des astuces intelligentes: en général, vous passez beaucoup de temps à optimiser quelque chose et ne constatez aucune ou une très petite diminution de la taille du code, car le compilateur l’aurait optimisé de toute façon.

Beaucoup de gens font l'erreur de disposer de variables qui stockent exactement la valeur numérique d'une unité pour laquelle elles utilisent cette variable: par exemple, leur variable time enregistre le nombre exact de millisecondes, même si la valeur ne dépasse pas 50 ms. pertinent. Peut-être que si votre variable représentait 50 ms pour chaque incrément de un, vous pourriez vous adapter à une variable plus petite ou égale à la taille du mot. Sur un système 8 bits, par exemple, même une simple addition de deux variables 32 bits génère une bonne quantité de code, en particulier si vous avez peu de registres, alors que les additions 8 bits sont à la fois petites et rapides.

4
vsz

Si vous avez beaucoup de calculs à virgule flottante très parallèles, en particulier en mathématiques à simple précision, essayez de les décharger vers un processeur graphique (le cas échéant) à l'aide d'OpenCL ou (pour les puces NVidia) CUDA. Les shaders des GPU ont une puissance de calcul considérable en virgule flottante, bien supérieure à celle d'un processeur.

4
Demi

passer par référence plutôt que par valeur

Modifiez le système d'exploitation et la structure.

Cela peut sembler exagéré, mais réfléchissez-y ainsi: les systèmes d’exploitation et les frameworks sont conçus pour faire beaucoup de choses. Votre application ne fait que des choses très spécifiques. Si vous pouviez obtenir le système d'exploitation qui réponde exactement aux besoins de votre application et si votre application comprenait comment fonctionne le framework (php, .net, Java), vous pourriez tirer un meilleur parti de votre matériel.

Facebook, par exemple, a modifié quelque chose au niveau du noya sous Linux, a modifié le fonctionnement de memcached (par exemple, ils ont écrit un proxy memcached, et tilisé udp au lieu de tcp ).

Window2008 est un autre exemple. Win2K8 a une version où vous pouvez installer uniquement le système d’exploitation de base nécessaire pour exécuter des applications X (par exemple, Web-Apps, Server Apps). Cela réduit en grande partie la charge de travail du système d'exploitation sur les processus en cours et améliore les performances.

Bien sûr, vous devriez toujours ajouter plus de matériel dans un premier temps ...

4
Nir Levy

Parfois, changer la disposition de vos données peut aider. En C, vous pouvez passer d'un tableau ou de structures à une structure de tableaux, ou inversement.

4
Nosredna

Une telle déclaration générale n'est pas possible, cela dépend du domaine du problème. Quelques possibilités:

Puisque vous ne spécifiez pas directement que votre demande est calculée à 100%:

  • Recherchez les appels qui bloquent (base de données, disque dur réseau, affichage de la mise à jour), isolez-les et/ou mettez-les en thread.

Si vous avez utilisé une base de données et qu'il s’agit bien de Microsoft SQL Server:

  • examinez les directives nolock et rowlock. (Il y a des discussions sur ce forum.)

SI votre application est purement calculatrice, vous pouvez vous pencher sur cette question de l’optimisation du cache pour la rotation de grandes images. L'augmentation de la vitesse m'a sidéré.

C’est long, mais cela donne peut-être une idée, surtout si votre problème concerne le domaine de l’imagerie: rotation-bitmaps-in-code

Une autre solution consiste à éviter autant que possible l’allocation dynamique de mémoire. Allouez plusieurs structures à la fois, libérez-les immédiatement.

Sinon, identifiez vos boucles les plus serrées et postez-les ici, en pseudo-pseudo ou non, avec certaines des infrastructures de données.

3

Dans un langage avec des modèles (C++/D), vous pouvez essayer de propager des valeurs constantes via des arguments de modèle. Vous pouvez même le faire pour de petits ensembles de valeurs pas vraiment constantes avec un commutateur.

Foo(i, j); // i always in 0-4.

devient

switch(i)
{
    case 0: Foo<0>(j); break;
    case 1: Foo<1>(j); break;
    case 2: Foo<2>(j); break;
    case 3: Foo<3>(j); break;
    case 4: Foo<4>(j); break;
}

L'inconvénient étant la pression du cache, il ne s'agirait donc que d'un gain dans les arbres d'appels longs ou profonds où la valeur est constante pour la durée.

3
BCS