Stratégies d'optimisation des performances de dernier recours [clôturé]


609

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 au problème et assez étroites. Et presque tous répètent les conseils 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 se faufiler jusqu'aux derniers pour cent dans un algorithme critique lorsqu'il ne reste plus qu'à faire quoi que ce soit.

Idéalement, essayez de rendre les réponses indépendantes du langage et indiquez les points négatifs des stratégies suggérées, le cas échéant.

Je vais ajouter une réponse avec mes propres suggestions initiales et j'attends avec impatience tout ce à quoi la communauté Stack Overflow peut penser.

Réponses:


427

OK, vous définissez le problème là où il semblerait qu'il n'y ait pas beaucoup de place à l'amélioration. C'est assez rare, d'après mon expérience. J'ai essayé d'expliquer cela dans un article du Dr Dobbs en novembre 1993, en partant d'un programme non trivial conventionnellement bien conçu sans gaspillage évident et en passant par une série d'optimisations jusqu'à ce que son temps d'horloge murale 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 le suivant . La séquence de changements était la suivante:

  • Le premier problème rencontré a été l'utilisation de clusters de listes (maintenant appelés "itérateurs" et "classes de conteneurs") représentant plus de la moitié du temps. Ceux-ci ont été remplacés par du code assez simple, ce qui a réduit le temps à 20 secondes.

  • Maintenant, le plus grand preneur de temps est plus de construction de listes. En pourcentage, il n'était pas si important auparavant, mais maintenant c'est parce que le plus gros problème a été supprimé. 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 que j'ai 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 je demande si toutes les recherches de liste qu'il fait sont réellement imposées par les exigences du problème.

Ensuite, je suis tombé sur une refonte, 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 trouve pas constamment des choses que le programmeur sait être assez prévisibles. En d'autres termes, n'interprétez pas la séquence des choses à faire, ne la "compilez" pas.

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

Maintenant, parce que ça devient si rapide, c'est difficile à échantillonner, donc je lui donne 10 fois plus de travail à faire, mais les temps suivants sont basés sur la charge de travail d'origine.

  • Plus de diagnostic révèle qu'il passe du temps à gérer les files d'attente. L'intégration de ces éléments réduit le temps à 7 secondes.

  • Maintenant, un grand preneur de temps est l'impression de diagnostic que je faisais. Rincer - 4 secondes.

  • Maintenant, les plus grands preneurs de temps sont les appels à 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 d'accélération total: 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. D'abord, vous obtenez les choses faciles, puis les plus difficiles, jusqu'à ce que vous arriviez à un point de rendements décroissants. Ensuite, les informations que vous obtiendrez pourraient bien conduire à une refonte, en commençant une nouvelle série d'accélérations, jusqu'à ce que vous atteigniez à nouveau des rendements décroissants. Maintenant, c'est le point auquel il pourrait être judicieux de se demander si ++iou i++ou for(;;)ouwhile(1) sont plus rapides: le genre de questions que je vois si souvent sur Stack Overflow.

PS 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, viennent à peine à l'idée que les instructions et les instructions d'appel sont plus importantes à localiser et plus faciles à corriger que des fonctions entières.

J'ai en fait construit un profileur pour le faire, mais pour une réelle intimité avec ce que fait le code, il n'y a pas de substitut pour mettre les doigts dedans. Ce n'est pas un problème que le nombre d'échantillons soit petit, car aucun des problèmes détectés n'est si minuscule qu'ils sont facilement ignorés.

AJOUT: jerryjvl a demandé quelques exemples. Voici le premier problème. Il se compose d'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)

Celles-ci utilisaient le cluster de listes ILST (similaire à une classe de liste). Ils sont mis en œuvre de la manière habituelle, la «dissimulation d'informations» signifiant que les utilisateurs de la classe n'étaient pas censés avoir à se soucier de la façon dont ils étaient mis en œuvre. Lorsque ces lignes ont été écrites (sur environ 800 lignes de code), on n'a pas pensé à l'idée qu'elles pouvaient être 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 d'après mon expérience, tous les problèmes de performances sont comme ça. En général, il est bon d'essayer d'éviter de créer des problèmes de performances. Il est encore mieux de trouver et de corriger ceux qui sont créés, même s'ils "auraient dû être évités" (avec le recul).

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

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

Il s'agit de créer des listes 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 seule fois.) La chose intéressante est que ces instructions ne coûtaient (c.-à-d. Étaient sur la pile des appels) 3/48 du temps d'origine, donc elles n'étaient pas dans fait un gros problème au début . Cependant, après avoir éliminé le premier problème, ils coûtaient 3/20 du temps et étaient donc maintenant un "plus gros poisson". En général, c'est comme ça que ça se passe.

Je pourrais ajouter que ce projet a été distillé d'un vrai projet sur lequel j'ai aidé. Dans ce projet, les problèmes de performances étaient beaucoup plus dramatiques (tout comme les accélérations), comme l'appel d'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, à la fois original et remanié, peut être trouvé sur www.ddj.com , pour 1993, dans le fichier 9311.zip, les 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 façon dont il a été réglé. Il ne passe que par la première moitié du scénario décrit ci-dessus, et il ne suit pas exactement la même séquence, mais obtient toujours une accélération de 2 à 3 ordres de grandeur.


3
J'adorerais lire certains détails des étapes que vous décrivez ci-dessus. Est-il possible d'inclure quelques fragments des optimisations pour la saveur? (sans rendre le message trop long?)
jerryjvl

8
... J'ai également écrit un livre qui est maintenant épuisé, donc ça va pour un prix ridicule sur Amazon - "Building Better Applications" ISBN 0442017405. Essentiellement, le même matériel est dans le premier chapitre.
Mike Dunlavey

3
@Mike Dunlavey, je suggère de dire à Google que vous l'avez déjà scanné. Ils ont probablement déjà un accord avec celui qui a acheté votre éditeur.
Thorbjørn Ravn Andersen

19
@ Thorbjørn: Juste pour faire un suivi, je me suis connecté avec GoogleBooks, j'ai rempli tous les formulaires et je leur ai envoyé une copie papier. J'ai reçu un e-mail me demandant si je détenais vraiment le droit d'auteur. L'éditeur Van Nostrand Reinhold, qui a été acheté par International Thompson, qui a été acheté par Reuters, et quand j'essaie de les appeler ou de leur envoyer un courriel, c'est comme un trou noir. C'est donc dans les limbes - je n'ai pas encore eu l'énergie pour vraiment le chasser.
Mike Dunlavey


188

Suggestions:

  • Pré-calculer plutôt que recalculer : toutes les boucles ou appels répétés qui contiennent des calculs qui ont une plage d'entrées relativement limitée, envisagez de faire une recherche (tableau ou dictionnaire) qui contient le résultat de ce calcul pour toutes les valeurs dans la plage valide de contributions. Utilisez ensuite une simple recherche à l'intérieur de l'algorithme.
    Inconvénients : si peu des valeurs précalculées sont réellement utilisées, cela peut aggraver les choses, la recherche peut également prendre beaucoup de mémoire.
  • N'utilisez pas de méthodes de bibliothèque : la plupart des bibliothèques doivent être écrites pour fonctionner correctement dans un large éventail de scénarios et effectuer des vérifications nulles des paramètres, etc. En réimplémentant une méthode, vous pourrez peut-être éliminer une grande partie de la logique qui ne s'applique pas dans les circonstances exactes où vous l'utilisez.
    Inconvénients : l'écriture de code supplémentaire signifie plus de surface pour les bogues.
  • Utilisez des méthodes de bibliothèque : pour me contredire, les bibliothèques de langues sont écrites par des gens beaucoup plus intelligents que vous ou moi; il y a de fortes chances qu'ils l'ont fait mieux et plus rapidement. Ne l'implémentez pas vous-même, sauf si vous pouvez réellement le rendre plus rapide (c'est-à-dire: toujours mesurer!)
  • Triche : dans certains cas, bien qu'un calcul exact puisse exister pour votre problème, vous n'aurez peut-être pas besoin de «exact», parfois une approximation peut être «assez bonne» et beaucoup plus rapide dans la transaction. Demandez-vous, est-ce vraiment important si la réponse est de 1%? 5%? même 10%?
    Inconvénients : Eh bien ... la réponse ne sera pas exacte.

32
Le précalcul n'aide pas toujours, et il peut même parfois faire mal - si votre table de recherche est trop grande, elle peut tuer les performances de votre cache.
Adam Rosenfield,

37
La tricherie peut souvent être la victoire. J'ai eu un processus de correction des couleurs qui au cœur était un vecteur 3 parsemé d'une matrice 3x3. Le processeur avait une matrice multipliée dans le matériel qui laissait de côté certains termes croisés et allait très vite par rapport à toutes les autres façons de le faire, mais ne supportait que les matrices 4x4 et 4 vecteurs de flotteurs. Changer le code pour transporter l'espace vide supplémentaire et convertir le calcul en virgule flottante à partir du point fixe a permis un résultat légèrement moins précis mais beaucoup plus rapide.
RBerteig

6
La triche consistait à utiliser une multiplication matricielle qui laissait de côté certains des produits internes, ce qui permettait de l'implémenter en microcode pour une seule instruction CPU qui se terminait plus rapidement que la séquence équivalente d'instructions individuelles. C'est une triche car il n'obtient pas la "bonne" réponse, juste une réponse qui est "assez correcte".
RBerteig

6
@RBerteig: juste "assez bien" est une opportunité d'optimisation que la plupart des gens manquent dans mon expérience.
Martin Thompson

5
Vous ne pouvez pas toujours supposer que tout le monde est plus intelligent que vous. Au final, nous sommes tous des professionnels. Vous pouvez cependant supposer qu'une bibliothèque spécifique que vous utilisez existe et a atteint votre environnement en raison de sa qualité, donc l'écriture de cette bibliothèque doit être très approfondie, vous ne pouvez pas le faire aussi bien uniquement parce que vous n'êtes pas spécialisé dans ce et vous n’y investissez pas le même temps. Pas parce que vous êtes moins intelligent. Allons.
v.oddou

164

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

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

Quelques exemples:

  • anticiper ce que l'utilisateur va demander et commencer à travailler avant
  • afficher les résultats à mesure qu'ils arrivent, au lieu de tout à la fois à la fin
  • Compteur de progression précis

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


27
Une barre de progression accélérant à la fin peut être perçue comme plus rapide qu'une barre absolument précise. Dans «Repenser la barre de progression» (2007), Harrison, Amento, Kuznetsov et Bell testent plusieurs types de barres sur un groupe d'utilisateurs et discutent de quelques façons de réorganiser les opérations afin que la progression puisse être perçue comme plus rapide.
Emil Vikström

9
naxa, la plupart des barres de progression sont fausses car il est difficile, voire impossible, de prévoir plusieurs étapes très différentes d'un flux en un seul pourcentage. Il suffit de regarder tous ces bars qui restent bloqués à 99% :-(
Emil Vikström

138

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

  • Le cache manque . 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 avoir une meilleure localité; compresser les structures et les types numériques pour éliminer les octets gaspillés (et donc les récupérations de cache gaspillées); prélecture des données dans la mesure du possible pour réduire les blocages.
  • Charger les magasins à succès . Les hypothèses du compilateur sur l'alias de pointeur et les cas où les données sont déplacées entre des ensembles de registres déconnectés via la mémoire peuvent provoquer un certain comportement pathologique qui provoque l'effacement de l'ensemble du pipeline CPU sur une opération de chargement. Trouvez les endroits où les flotteurs, les vecteurs et les encres sont coulés les uns aux autres et éliminez-les. Utilisez __restrictgénéreusement pour promettre au compilateur d'alias.
  • Opérations microcodées . La plupart des processeurs ont certaines opérations qui ne peuvent pas être mises en pipeline, mais exécutent à la place un minuscule sous-programme stocké dans la ROM. Les exemples sur le PowerPC sont la multiplication, la division et le décalage par quantité variable. Le problème est que l'ensemble du pipeline s'arrête net 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 pipelinées constitutives afin que vous puissiez bénéficier de la répartition superscalaire sur tout ce que fait le reste de votre programme.
  • Mauvais pronostics de la branche . Celles-ci aussi vident le pipeline. Trouvez des cas où le processeur passe beaucoup de temps à remplir le tuyau après une branche, et utilisez des indications de branche si elles sont disponibles pour obtenir une prévision correcte plus souvent. Ou mieux encore, remplacez les branches par des mouvements conditionnels dans la mesure du possible, en particulier après les opérations en virgule flottante car leur canal est généralement plus profond et la lecture des indicateurs de condition après fcmp peut provoquer un blocage.
  • Opérations séquentielles à virgule flottante . Faites ces SIMD.

Et encore une chose que j'aime faire:

  • Configurez votre compilateur pour générer des listes d'assemblys et regardez ce qu'il émet pour les fonctions de hotspot dans votre code. Toutes ces optimisations intelligentes qu'un "bon compilateur devrait être capable de faire automatiquement pour vous"? Les chances sont que votre compilateur réel ne les fait pas. J'ai vu GCC émettre vraiment du code WTF.

8
J'utilise principalement Intel VTune et PIX. Aucune idée s'ils peuvent s'adapter à C #, mais vraiment une fois que vous avez cette couche d'abstraction JIT, la plupart de ces optimisations sont hors de votre portée, sauf pour améliorer la localisation du cache et peut-être éviter certaines branches.
Crashworks

6
Même ainsi, la vérification de la sortie post-JIT peut aider à déterminer s'il existe des constructions qui ne s'optimisent tout simplement pas bien au cours de la phase JIT ... l'enquête ne peut jamais faire de mal, même si elle se révèle une impasse.
jerryjvl

5
Je pense que beaucoup de gens, dont moi-même, seraient intéressés par cet "assemblage wtf" produit par gcc. Le vôtre semble être un travail très intéressant :)
BlueRaja - Danny Pflughoeft

1
Examples on the PowerPC ...<- Autrement dit, certaines implémentations de PowerPC. PowerPC est un ISA, pas un CPU.
Billy ONeal

1
@BillyONeal Même sur du matériel x86 moderne, imul peut bloquer le pipeline; voir «Intel® 64 et IA-32 Architectures Optimization Reference Manual» §13.3.2.3: «L'instruction de multiplication entière prend plusieurs cycles à exécuter. phase d'exécution. Toutefois, les instructions de multiplication d'entiers empêcheront l'émission d'autres instructions d'entier à un seul cycle en raison de l'exigence de l'ordre du programme. " C'est pourquoi il est généralement préférable d'utiliser des tailles de tableau alignées sur des mots et lea.
Crashworks

78

Jetez plus de matériel dessus!


30
plus de matériel n'est pas toujours une option lorsque vous avez un logiciel qui devrait s'exécuter sur du matériel déjà sur le terrain.
Doug T.29

76
Pas une réponse très utile à quelqu'un qui crée des logiciels grand public: le client ne voudra pas vous entendre dire "achetez un ordinateur plus rapide". Surtout si vous écrivez un logiciel pour cibler quelque chose comme une console de jeu vidéo.
Crashworks

19
@Crashworks, ou d'ailleurs, un système embarqué. Lorsque la dernière fonctionnalité est enfin disponible et que le premier lot de cartes est déjà tourné, ce n'est pas le moment de découvrir que vous auriez dû utiliser un processeur plus rapide en premier lieu ...
RBerteig

71
Une fois, j'ai dû déboguer un programme qui avait une énorme fuite de mémoire - sa taille de machine virtuelle a augmenté d'environ 1 Mo par heure. Un collègue a plaisanté en disant que tout ce que j'avais à faire était d'ajouter de la mémoire à un rythme constant . :)
j_random_hacker

9
Plus de matériel: ah oui, la bouée de sauvetage du développeur médiocre. Je ne sais pas combien de fois j'ai entendu "ajouter une autre machine et doubler la capacité!"
Olof Forshell

58

Plus de suggestions:

  • Évitez les E / S : toute E / S (disque, réseau, ports, etc.) sera toujours beaucoup plus lente que tout code effectuant des calculs, alors débarrassez-vous de toutes les E / S dont vous n'avez pas strictement besoin.

  • Déplacer les E / S à l'avant : chargez toutes les données dont vous aurez besoin pour un calcul à l'avance, de sorte que vous n'ayez pas d'attentes d'E / S répétées dans le cœur d'un algorithme critique (et peut-être par conséquent répétées recherche de disque, lorsque le chargement de toutes les données en une seule fois peut éviter de chercher).

  • Retardez les E / S : n'écrivez pas vos résultats avant la fin du calcul, stockez-les dans une structure de données, puis enregistrez-les en une seule fois à la fin lorsque le travail est terminé.

  • E / S filetées : pour ceux qui osent assez, combinez les `` E / S à l'avance '' ou les `` E / S différées '' avec le calcul réel en déplaçant le chargement dans un thread parallèle, de sorte que pendant que vous chargez plus de données, vous pouvez travailler sur un calcul sur les données que vous avez déjà, ou pendant que vous calculez le prochain lot de données, vous pouvez simultanément écrire les résultats du dernier lot.


3
Notez que «déplacer l'IO vers un thread parallèle» doit être effectué en tant qu'IO asynchrone sur de nombreuses plates-formes (par exemple Windows NT).
Billy ONeal

2
Les E / S sont en effet un point critique, car elles sont lentes et ont des latences énormes, et vous pouvez aller plus vite avec ce conseil, mais il est toujours fondamentalement défectueux: les points sont la latence (qui doit être cachée) et la surcharge de l'appel système ( qui doit être réduit en réduisant le nombre d'appels d'E / S). Le meilleur conseil est: utiliser mmap()pour l'entrée, faire des madvise()appels appropriés et utiliser aio_write()pour écrire de gros morceaux de sortie (= quelques Mio).
cmaster - réintègre monica le

1
Cette dernière option est assez facile à implémenter en Java, notamment. Cela a donné d'énormes augmentations de performances pour les applications que j'ai écrites. Un autre point important (plus que le déplacement d'E / S en amont) est de le rendre SÉQUENTIEL et d'E / S à gros blocs. Beaucoup de petites lectures coûtent beaucoup plus cher qu'une seule grande, en raison du temps de recherche de disque.
BobMcGee

À un moment donné, j'ai triché en évitant les E / S, en déplaçant temporairement tous les fichiers sur un disque RAM avant le calcul et en les déplaçant ensuite. Ceci est sale, mais peut être utile dans une situation où vous ne contrôlez pas la logique qui effectue les appels d'E / S.
MD

48

É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 spécifiques à examiner 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 également les boucles. La plupart du temps, l'accès aux données doit être basé sur un ensemble, et non pas enregistrement par enregistrement. Cela inclut de ne pas réutiliser une seule procédure stockée d'enregistrements lorsque vous souhaitez insérer 1 000 000 d'enregistrements à la fois.

N'utilisez jamais select *, renvoyez uniquement 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 et entraîneront ainsi une charge inutile sur le serveur et le réseau.

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

Index, index, index. Et obtenez ces statistiques mises à jour si elles s'appliquent à votre base de données.

Rendez la requête sargable . Cela signifie éviter les choses qui rendent impossible l'utilisation des index, comme 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 des types de données corrects. Il est plus rapide de faire des calculs de date sur un champ de date que d'essayer de convertir un type de données chaîne en un type de données date, puis de faire le calcul.

Ne jamais mettre une boucle d'aucune sorte dans un déclencheur!

La plupart des bases de données ont 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 trouvent les problèmes.

Tenez compte de la fréquence d'exécution de la requête ainsi que du temps nécessaire à son exécution pour déterminer ce qui doit être optimisé. Parfois, vous pouvez obtenir plus de performances d'un léger ajustement à une requête qui s'exécute des millions de fois par jour que d'essuyer une heure de longue durée 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 depuis la base de données. Je me souviens d'une fois dans le passé où nous ne pouvions pas comprendre pourquoi la page était si lente à charger lorsque la procédure stockée était rapide et avait découvert grâce au profilage que 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 tout en s'exécutant seules peuvent devenir très lentes en raison des verrous d'autres requêtes.


29

Le facteur limitant le plus important aujourd'hui est la bande passante à mémoire limitée . Les multicœurs 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 œuvre des caches est également divisée entre les cœurs et les threads, ce qui aggrave encore ce problème. Enfin, la signalisation inter-puce nécessaire pour maintenir la cohérence des différents caches augmente également avec un nombre accru de cœurs. Cela ajoute également une pénalité.

Ce sont les effets que vous devez gérer. Parfois par la micro-gestion de votre code, mais parfois par une réflexion et une refactorisation soigneuses.

De nombreux commentaires mentionnent déjà un code compatible avec le cache. Il existe au moins deux saveurs distinctes:

  • Évitez les latences de récupération de mémoire.
  • Réduction de la pression du bus mémoire (bande passante).

Le premier problème concerne spécifiquement la régularisation de vos modèles d'accès aux données, permettant au préfetcher matériel de fonctionner efficacement. Évitez l'allocation dynamique de mémoire qui répartit vos objets de données dans la mémoire. Utilisez des conteneurs linéaires au lieu de listes, de hachages et d'arbres liés.

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 pendant qu'elles sont encore dans le cache.

Le fait de regrouper les données plus étroitement et de vous assurer que vous utilisez toutes les données dans les lignes de cache dans les boucles actives, vous aidera à éviter ces autres effets et permettra d'ajuster des données plus utiles dans le cache.


25
  • Sur quel matériel utilisez-vous? Pouvez-vous utiliser des optimisations spécifiques à la plate-forme (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 plateforme. Dans le style de "à un if / else, mettez d'abord la déclaration la plus courante"

4
Devrait être "passer de GCC à LLVM" :)
Zifre

4
Pouvez-vous faire fonctionner votre algorithme en parallèle? - l'inverse s'applique également
juste le

4
Il est vrai que la réduction de la quantité de threads peut être une aussi bonne optimisation
Johan Kotlinski

re: micro-optimisation: si vous vérifiez la sortie asm du compilateur, vous pouvez souvent modifier la source pour la tenir à la main afin de produire un meilleur asm. Voir Pourquoi ce code C ++ est-il plus rapide que mon assemblage manuscrit pour tester la conjecture Collatz? pour en savoir plus sur l'aide ou le battement du compilateur sur x86 moderne.
Peter Cordes

17

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 porcs du temps qui vous aide à comprendre où vous devez affiner votre algorithme. C'est la seule réponse agnostique de langage globale que je puisse trouver à un problème qui est déjà censé être entièrement optimisé. En supposant également que vous souhaitiez être indépendant de l'architecture dans votre quête de vitesse.

Ainsi, alors que l'algorithme peut être optimisé, sa mise en œuvre peut ne pas l'être. L'identification vous permet de savoir de quelle pièce il s'agit: algorithme ou implémentation. Donc, selon le nombre de porcs le plus important, il est votre principal candidat à l'examen. Mais puisque vous dites que vous voulez extraire les derniers%, vous voudrez peut-être également examiner les parties moindres, les parties que vous n'avez pas examinées de si près au début.

Enfin, un peu d'essais et d'erreurs avec des chiffres de performances sur différentes façons de mettre en œuvre la même solution, ou des algorithmes potentiellement différents, peuvent apporter des informations qui aident à identifier les pertes de temps et les gains de temps.

HPH, asoudmove.


16

Vous devriez probablement considérer la "perspective Google", c'est-à-dire déterminer comment votre application peut devenir largement parallélisée et concurrente, ce qui impliquera inévitablement à un moment donné d'envisager de distribuer votre application sur différentes machines et réseaux, afin qu'elle puisse idéalement évoluer presque linéairement avec le matériel que vous lui lancez.

D'autre part, les gens de Google sont également connus pour avoir déployé beaucoup de main-d'œuvre et de ressources pour résoudre certains des problèmes dans les projets, les outils et l'infrastructure qu'ils utilisent, comme par exemple l'optimisation de l'ensemble du programme pour gcc en ayant une équipe d'ingénieurs dédiée pirater les internes de gcc afin de le préparer aux scénarios d'utilisation typiques de Google.

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


15
  • Routines en ligne (éliminer l'appel / retour et la poussée des paramètres)
  • Essayez d'éliminer les tests / commutateurs avec des recherches de table (si elles sont plus rapides)
  • Dérouler les boucles (le périphérique de Duff) au point où elles tiennent simplement dans le cache CPU
  • Localisez l'accès à la mémoire pour ne pas faire exploser votre cache
  • Localisez les calculs associés si l'optimiseur ne le fait pas déjà
  • Élimine les invariants de boucle si l'optimiseur ne le fait pas déjà

2
L'appareil de l'IIRC Duff est très rarement plus rapide. Uniquement lorsque l'opération est très courte (comme une seule petite expression mathématique)
BCS

12
  • Lorsque vous arrivez au point d'utiliser des algorithmes efficaces, il s'agit de savoir ce dont vous avez besoin de plus de vitesse ou de mémoire . Utilisez la mise en cache pour «payer» en mémoire pour plus de vitesse ou utilisez des calculs pour réduire l'empreinte mémoire.
  • Si possible (et plus rentable), lancez le matériel sur le problème - un processeur plus rapide, plus de mémoire ou HD pourrait 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, en utilisant du code managé (c'est-à-dire Java / .NET) accélèrent le développement mais les langages de programmation natifs créent un code plus rapide.
  • Micro optimisation . Seuls étaient applicables, vous pouvez utiliser un assemblage optimisé pour accélérer de petits morceaux de code, l'utilisation d'optimisations SSE / vectorielles aux bons endroits peut augmenter considérablement les performances.

12

Diviser et conquérir

Si l'ensemble de données en cours de traitement est trop volumineux, passez en boucle sur des morceaux de celui-ci. Si vous avez bien fait votre code, l'implémentation devrait être facile. Si vous avez un programme monolithique, vous savez maintenant mieux.


9
+1 pour le son "smack" de la tapette à mouches que j'ai entendu en lisant la dernière phrase.
Bryan Boettcher

11

Tout d'abord, comme mentionné dans plusieurs réponses précédentes, découvrez ce qui mord vos performances - est-ce la mémoire ou le processeur ou le réseau ou la base de données ou autre chose. En fonction de cela ...

  • ... si c'est de la mémoire - trouvez l'un des livres écrits il y a longtemps par Knuth, l'un 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 mauvaise, vous devrez découvrir dans lequel il explique comment gérer le stockage lent des données sur bande. Transformer mentalement sa mémoire / bande paire en votre paire de cache / mémoire principale (ou en paire de cache L1 / L2) respectivement. Étudiez toutes les astuces qu'il décrit - si vous ne trouvez pas quelque chose qui résout votre problème, alors embauchez un informaticien professionnel pour mener une recherche professionnelle. Si votre problème de mémoire est par hasard avec la FFT (le cache manque aux index à bits inversés lorsque vous faites des papillons radix-2), alors n'embauchez pas de scientifique - à la place, optimisez manuellement les passes une par une jusqu'à ce que vous '' évincer jusqu'aux derniers pour cent vrai? Si c'est peu, vous gagnerez probablement.

  • ... s'il s'agit d'un processeur - passez au langage d'assemblage. Étudiez les spécifications du processeur - données - par exemple, vos calculs de flottement peuvent s'avérer plus lents que les doubles pour un processeur particulier. Si vous avez des trucs trigonométriques, combattez-les avec des tables pré-calculées; gardez également à l'esprit que le sinus de petite valeur peut être remplacé par cette valeur si la perte de précision se situe dans les limites autorisées. ce qui prend des tiques , 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 remplaçables par des ajouts). Essayez des astuces avec des données plus courtes - si vous êtes chanceux, une instruction avec 64 bits peut s'avérer remplaçable par deux sur 32 ou même 4 sur 16 ou 8 sur 8 bits. Essayez aussi plus longtemps

  • ... s'il s'agit d'un réseau - pensez à compresser les données que vous passez dessus. Remplacez le transfert XML par binaire. Protocoles d'étude. Essayez UDP au lieu de TCP si vous pouvez en quelque sorte gérer la perte de données.

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

HTH :)


9

Caching!Un moyen peu coûteux (dans l'effort du programmeur) d'accélérer presque n'importe quoi est d'ajouter une couche d'abstraction de mise en cache à n'importe quelle zone de mouvement de données de votre programme. Qu'il s'agisse d'E / S ou simplement de passage / création d'objets ou de structures. Il est souvent facile d'ajouter des caches aux classes d'usine et aux lecteurs / rédacteurs.

Parfois, le cache ne vous rapportera pas grand-chose, mais c'est une méthode simple pour simplement ajouter le cache partout, puis le désactiver là où cela n'aide pas. J'ai souvent trouvé cela pour gagner des performances énormes sans avoir à micro-analyser le code.


8

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

Cela peut sembler évident pour certains, mais c'est quelque chose sur lequel j'essaie de me concentrer indépendamment de la langue avec laquelle je travaille. Si vous avez affaire à des boucles imbriquées, par exemple, et que vous trouvez la possibilité de réduire le code d'un niveau, vous pouvez dans certains cas accélérer considérablement votre code. Comme autre exemple, il y a les petites choses à penser comme travailler avec des entiers au lieu de variables à virgule flottante chaque fois que vous le pouvez, et utiliser la multiplication au lieu de la division chaque fois que vous le pouvez. Encore une fois, ce sont des choses qui devraient être prises en compte pour votre boucle la plus intérieure.

Parfois, il peut être avantageux d'effectuer vos opérations mathématiques sur un 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 sacrifier la vitesse dans une section pour améliorer la vitesse dans une autre, mais dans certains cas, le gain peut en valoir la peine.


8

J'ai passé un peu de temps à optimiser les systèmes d'entreprise client / serveur fonctionnant sur des réseaux à faible bande passante et à longue latence (par exemple satellite, distant, offshore), et j'ai pu obtenir des améliorations de performances spectaculaires avec un processus assez répétable.

  • Mesurer : Commencez par comprendre la capacité et la topologie sous-jacentes du réseau. Parler aux personnes en réseau concernées dans l'entreprise et utiliser des outils de base tels que ping et traceroute pour établir (au minimum) la latence du réseau à partir de chaque emplacement client, pendant les périodes opérationnelles typiques. Ensuite, prenez des mesures précises du temps de fonctions spécifiques de l'utilisateur final qui affichent les symptômes problématiques. Enregistrez toutes ces mesures, ainsi que leurs emplacements, dates et heures. Envisagez d'intégrer la fonctionnalité de «test des performances réseau» de l'utilisateur final dans votre application client, permettant à vos utilisateurs expérimentés de participer au processus d'amélioration; leur donner de telles capacités peut avoir un impact psychologique énorme lorsque vous avez affaire à des utilisateurs frustrés par un système peu performant.

  • Analyser : en utilisant toutes les méthodes de journalisation disponibles pour établir exactement quelles données sont transmises et reçues pendant l'exécution des opérations affectées. Idéalement, votre application peut capturer des données transmises et reçues à la fois par le client et le serveur. Si ceux-ci incluent également des horodatages, c'est encore mieux. Si une journalisation suffisante n'est pas disponible (par exemple, système fermé ou incapacité à 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 rarement modifiées sont transmises de manière répétitive et envisagez une stratégie de mise en cache appropriée. Les exemples typiques incluent les valeurs de «liste de choix» ou d'autres «entités de référence», qui peuvent être étonnamment grandes dans certaines applications commerciales. Dans de nombreux cas, les utilisateurs peuvent accepter qu'ils doivent redémarrer ou actualiser l'application pour mettre à jour des données rarement mises à jour, surtout si cela peut réduire considérablement le temps d'affichage des éléments d'interface utilisateur couramment utilisés. Assurez-vous de 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 toujours un aller-retour réseau pour garantir la cohérence, et lorsque la latence du réseau est coûteuse, vous pourrez peut-être l'éviter complètement avec une approche de mise en cache différente.

  • Paralléliser : recherchez les transactions séquentielles qui n'ont logiquement pas besoin d'être émises strictement séquentiellement et retravaillez le système pour les émettre en parallèle. J'ai traité un cas où une demande de bout en bout avait un retard de réseau inhérent de ~ 2 s, ce qui n'était pas un problème pour une seule transaction, mais lorsque 6 allers-retours séquentiels de 2 s étaient nécessaires avant que l'utilisateur ne reprenne le contrôle de l'application cliente , il est devenu une énorme source de frustration. Le fait de découvrir que ces transactions étaient en fait indépendantes a permis de les exécuter en parallèle, ce qui a réduit le délai de l'utilisateur final à très près du coût d'un aller-retour unique.

  • Combiner : où les demandes séquentielles doivent être exécutées séquentiellement, recherchez les opportunités de les combiner en une seule demande plus complète. Les exemples typiques incluent la création de nouvelles entités, suivies des demandes de mise en relation de ces entités avec d'autres entités existantes.

  • Compresser : recherchez des opportunités pour tirer parti de la compression de la charge utile, soit en remplaçant un formulaire textuel par un formulaire binaire, soit en utilisant une technologie de compression réelle. De nombreuses piles technologiques modernes (c'est-à-dire dans une décennie) prennent en charge cela de manière presque transparente, alors assurez-vous qu'il est configuré. J'ai souvent été surpris par l'impact significatif de la compression où il semblait clair que le problème était fondamentalement la latence plutôt que la bande passante, découvrant après le fait qu'il permettait à la transaction de tenir dans un seul paquet ou d'éviter autrement la perte de paquets et donc d'avoir une taille excessive impact sur les performances.

  • Répétition : retournez au début et mesurez à nouveau vos opérations (aux mêmes endroits et aux mêmes moments) avec les améliorations en place, enregistrez et rapportez vos résultats. Comme pour toute optimisation, certains problèmes peuvent avoir été résolus, ce qui en expose d'autres qui dominent désormais.

Dans les étapes ci-dessus, je me concentre sur le processus d'optimisation lié à l'application, mais bien sûr, vous devez vous assurer que le réseau sous-jacent lui-même est configuré de la manière la plus efficace pour prendre en charge votre application également. Engagez les spécialistes de la mise en réseau dans l'entreprise et déterminez s'ils sont en mesure d'appliquer des améliorations de capacité, la qualité de service, la compression du 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 équipé (après l'étape Analyser) pour en discuter avec eux, et également pour faire l'analyse de rentabilisation de tous les coûts que vous allez leur demander d'engager. . J'ai rencontré des cas où une configuration réseau erronée provoquait la transmission des données des applications sur une liaison satellite lente plutôt que sur une liaison terrestre, tout simplement parce qu'il utilisait un port TCP qui n'était pas "bien connu" des spécialistes des réseaux; de toute évidence, la correction d'un problème comme celui-ci peut avoir un impact dramatique sur les performances, sans aucun code logiciel ou changement de configuration nécessaire.


7

Très difficile de donner une réponse générique à cette question. Cela dépend vraiment de votre domaine de problème et de votre implémentation technique. Une technique générale qui est assez neutre en termes de langage: identifiez les points chauds de code qui ne peuvent pas être éliminés et optimisez manuellement le code assembleur.


7

Les derniers% sont très dépendants du CPU et des applications ....

  • les architectures de cache diffèrent, certaines puces ont une RAM sur puce que vous pouvez mapper directement, les ARM ont (parfois) une unité vectorielle, SH4 est un opcode matriciel utile. Y a-t-il un GPU - peut-être qu'un shader est le chemin à parcourir. Les TMS320 sont très sensibles aux branches dans les boucles (séparez donc les boucles et déplacez les conditions à l'extérieur si possible).

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

Générez pour x86 et exécutez Valgrind / Cachegrind par rapport au code pour un profilage des performances approprié. Ou CCStudio de Texas Instruments a un profileur doux. Ensuite, vous saurez vraiment où vous concentrer ...


7

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 ligne, tout en ayant le meilleur logiciel et le meilleur matériel, si votre débit est faible, cette ligne mince va compresser les données et vous donner des retards, bien qu'en millisecondes ... mais si vous parlez des dernières gouttes , c'est quelques gouttes gagnées, 24/7 pour tout paquet envoyé ou reçu.


7

Pas aussi en profondeur ou complexe que les réponses précédentes, mais voici: (ce sont plus de niveau 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
  • utilisez des opérateurs au niveau du bit chaque fois que vous le pouvez
  • décomposer le code répétitif en modules / fonctions
  • objets de cache
  • les variables locales ont un léger avantage en termes de performances
  • limiter autant que possible la manipulation des chaînes

4
À propos du bouclage en arrière: oui, la comparaison pour la fin de la boucle sera plus rapide. En règle générale, vous utilisez la variable pour indexer dans la mémoire, et y accéder peut être contre-productif en raison de fréquents échecs de cache (pas de prélecture).
Andreas Reiff

1
AFAIK, dans la plupart des cas, tout optimiseur raisonnable fera très bien avec les boucles, sans que le programmeur ait à s'exécuter explicitement en sens inverse. Soit l'optimiseur inversera la boucle elle-même, soit il aura une autre méthode tout aussi bonne. J'ai noté une sortie ASM identique pour les boucles (certes relativement simples) écrites à la fois ascendant vs max et descendant vs 0. Bien sûr, mes jours Z80 m'ont habitué à écrire réflexivement des boucles en arrière, mais je soupçonne que le mentionner aux débutants est généralement un red harging / optimisation prématurée, lorsque le code lisible et l'apprentissage de pratiques plus importantes devraient être des priorités.
underscore_d

Au contraire, l'exécution d'une boucle en arrière sera plus lente dans les langues de niveau inférieur car dans une guerre entre la comparaison à zéro et la soustraction supplémentaire par rapport à une comparaison à un seul entier, la comparaison à un seul entier est plus rapide. Au lieu de décrémenter, vous pouvez avoir un pointeur vers l'adresse de début en mémoire et un pointeur vers l'adresse de fin en mémoire. Ensuite, incrémentez le pointeur de début jusqu'à ce qu'il soit égal au pointeur de fin. Cela éliminera l'opération de décalage de mémoire supplémentaire dans le code d'assemblage, s'avérant ainsi beaucoup plus performante.
Jack Giffin

5

Impossible à dire. Cela dépend de l'apparence du code. Si nous pouvons supposer que le code existe déjà, alors nous pouvons simplement le regarder et comprendre à partir de cela, comment l'optimiser.

Meilleure localité de cache, déroulement de boucle, essayez d'éliminer les longues chaînes de dépendance, pour obtenir un meilleur parallélisme au niveau de l'instruction. Préférez les déplacements conditionnels aux branches lorsque cela est possible. Exploitez les instructions SIMD lorsque cela est possible.

Comprenez ce que fait votre code et comprenez le matériel sur lequel il fonctionne. Ensuite, il devient 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.

Eh bien, cela et "Afficher le code sur SO et demander des conseils d'optimisation pour ce morceau de code spécifique".


5

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

  • Vérifiez que vous utilisez les meilleures options de compilateur et de l'éditeur de liens.
  • Si la routine de point d'accès dans une bibliothèque différente de celle d'un appelant fréquent, envisagez de la déplacer ou de la cloner vers le module d'appelants. Élimine une partie de la surcharge des appels et peut améliorer les accès au cache (cf. comment AIX lie statiquement strcpy () en objets partagés liés séparément). Bien sûr, cela pourrait également réduire les accès au cache, c'est 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. L'inconvénient est plus d'une version à maintenir.
  • Regardez 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?

J'ajouterais à votre premier paragraphe: n'oubliez pas de désactiver toutes les informations de débogage dans vos options de compilation .
varnie

5

La manière google est une option "Cachez-le .. Dans la mesure du possible, ne touchez pas le disque"


5

Voici quelques techniques d'optimisation rapides et sales que j'utilise. Je considère qu'il s'agit d'une optimisation de «premier passage».

Apprenez où le temps est passé Découvrez exactement ce qui prend le temps. S'agit-il d'un 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 Savoir où optimiser 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 quelque chose de différent si un code retour indique un échec, tandis que dans Dot Net, intercepter des exceptions est beaucoup plus lent que de vérifier une condition valide avant de tenter.

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

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

Réduisez au minimum les tentatives d' E / S de manière à réduire le nombre de fois où vous devez lire ou écrire, en particulier via une connexion 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)

Threads spawn pour les projets avec une interface utilisateur, fraye un nouveau thread pour préformer des tâches plus lentes rend l'application sensation plus sensible, bien que n'est pas.

Pré-processus Vous pouvez généralement échanger de l'espace contre de la vitesse. S'il y a des calculs ou d'autres opérations intenses, voyez si vous pouvez précalculer certaines des informations avant d'être dans la boucle critique.


5

Si vous avez beaucoup de mathématiques en virgule flottante hautement parallèles, en particulier en simple précision, essayez de les décharger sur un processeur graphique (le cas échéant) en utilisant OpenCL ou (pour les puces NVidia) CUDA. Les GPU ont une immense puissance de calcul en virgule flottante dans leurs shaders, qui est beaucoup plus grande que celle d'un CPU.


5

Ajout de cette réponse car je ne l'ai pas vue incluse dans toutes les autres.

Minimisez la conversion implicite entre les types et le signe:

Cela s'applique au moins à C / C ++, même si vous pensez déjà vous êtes libre de conversions - parfois il est bon de tester l'ajout d'avertissements du compilateur autour de fonctions qui nécessitent des performances, en particulier attention aux conversions dans les boucles.

GCC spesific: vous pouvez tester cela 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 où vous pouvez obtenir une 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 éviter les conversions accidentelles, mais c'est un compromis car vous pouvez finir par ajouter beaucoup de transtypages à des conversions intentionnelles silencieuses qui peuvent simplement rendre le code plus encombré pour un minimum gains.


C'est pourquoi j'aime ça dans OCaml, le cast entre les types numériques doit être xplicit.
Gaius

@Gaius fair point - mais dans de nombreux cas, changer de langue n'est pas un choix réaliste. Puisque C / C ++ sont si largement utilisés, il est utile de pouvoir les rendre plus stricts, même s'ils sont spécifiques au compilateur.
ideasman42

4

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


4

Ajustez le système d'exploitation et le cadre.

Cela peut sembler exagéré, mais pensez-y comme ceci: les systèmes d'exploitation et les cadres sont conçus pour faire beaucoup de choses. Votre application ne fait que des choses très spécifiques. Si vous pouviez faire en sorte que le système d'exploitation réponde exactement aux besoins de votre application et que votre application comprenne comment fonctionne le framework (php, .net, java), vous pourriez tirer un meilleur parti de votre matériel.

Facebook, par exemple, a changé certains trucs au niveau du noyau sous Linux, changé le fonctionnement de memcached (par exemple, ils ont écrit un proxy memcached et utilisé udp au lieu de tcp ).

Window2008 en 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 les applications X (par exemple, Web-Apps, Server Apps). Cela réduit une grande partie des frais généraux que le système d'exploitation a sur les processus en cours d'exécution et vous donne de meilleures performances.

Bien sûr, vous devez toujours ajouter plus de matériel comme première étape ...


2
Ce serait une approche valable après l'échec de toutes les autres approches, ou si un système d'exploitation ou une fonctionnalité spécifique était responsable d'une baisse sensible des performances, mais le niveau d'expertise et de contrôle nécessaire pour y parvenir peut ne pas être disponible pour chaque projet.
Andrew Neely
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.