Il semble que l'optimisation soit un art perdu de nos jours. N'y a-t-il pas eu un moment où tous les programmeurs ont extrait chaque once d'efficacité de leur code? Le faisant souvent en marchant cinq milles dans la neige?
Dans l'esprit de ramener un art perdu, quelles sont les astuces que vous connaissez pour des changements simples (ou peut-être complexes) pour optimiser le code C # /. NET? Étant donné que c'est une chose si large qui dépend de ce que l'on essaie d'accomplir, il serait utile de fournir un contexte à votre conseil. Par exemple:
StringBuilder
. Voir le lien en bas pour les mises en garde à ce sujet.string.Compare
Pour comparer deux chaînes au lieu de faire quelque chose comme string1.ToLower() == string2.ToLower()
Jusqu'à présent, le consensus général semble mesurer est essentiel. Ce genre de manque le point: mesurer ne vous dit pas ce qui ne va pas, ou quoi faire si vous rencontrez un goulot d'étranglement. J'ai rencontré une fois le goulot d'étranglement de la concaténation de chaînes et je ne savais pas quoi faire à ce sujet, ces conseils sont donc utiles.
Mon point, même pour publier ceci, est d'avoir un endroit pour les goulots d'étranglement communs et comment ils peuvent être évités avant même de les rencontrer. Il ne s'agit même pas nécessairement de code plug and play que quiconque devrait suivre aveuglément, mais plutôt de comprendre que les performances doivent être prises en compte, au moins un peu, et qu'il y a des pièges communs à rechercher.
Je peux voir cependant qu'il pourrait être utile de savoir également pourquoi un pourboire est utile et où il doit être appliqué. Pour l'astuce StringBuilder
, j'ai trouvé l'aide que j'ai faite il y a longtemps sur ici sur le site de Jon Skeet .
Il semble que l'optimisation soit un art perdu de nos jours.
Il y avait une fois par jour où la fabrication, par exemple, de microscopes était pratiquée comme un art. Les principes optiques étaient mal compris. Il n'y a pas eu de standardisation des pièces. Les tubes, les engrenages et les lentilles devaient être fabriqués à la main par des travailleurs hautement qualifiés.
De nos jours, les microscopes sont produits comme une discipline d'ingénierie. Les principes sous-jacents de la physique sont extrêmement bien compris, les pièces standard sont largement disponibles et les ingénieurs en construction de microscopes peuvent faire des choix éclairés quant à la meilleure façon d'optimiser leur instrument pour les tâches qu'il est conçu pour effectuer.
Que l'analyse des performances soit un "art perdu" est une très, très bonne chose. Cet art était pratiqué comme un art . L'optimisation doit être approchée pour ce qu'elle est: un problème d'ingénierie pouvant être résolu par l'application rigoureuse de principes d'ingénierie solides.
Au fil des ans, on m'a demandé des dizaines de fois ma liste de "trucs et astuces" que les gens peuvent utiliser pour optimiser leur vbscript/leur jscript/leurs pages de serveur actives/leur VB/leur C # code. Je résiste toujours à cela. Mettre l'accent sur les "trucs et astuces" est exactement la mauvaise façon d'approcher les performances. De cette façon, cela conduit à un code qui est difficile à comprendre, difficile à raisonner, difficile à maintenir, qui n'est généralement pas plus rapide que le code simple correspondant.
La bonne façon d'approcher la performance est de l'aborder comme un problème d'ingénierie comme tout autre problème:
C'est la même chose que vous résolvez tout autre problème d'ingénierie, comme l'ajout d'une fonctionnalité - définissez des objectifs axés sur le client pour la fonctionnalité, suivez les progrès de la réalisation d'une implémentation solide, résolvez les problèmes lorsque vous les trouvez grâce à une analyse de débogage minutieuse, continuez à répéter jusqu'à ce que vous expédiez ou échouez. Les performances sont une fonctionnalité.
L'analyse des performances sur des systèmes modernes complexes nécessite de la discipline et se concentre sur des principes d'ingénierie solides, et non sur un sac rempli de trucs qui sont étroitement applicables à des situations triviales ou irréalistes. Je n'ai jamais résolu un seul problème de performances dans le monde réel grâce à l'application de trucs et astuces.
Obtenez un bon profileur.
Ne vous embêtez même pas à essayer d'optimiser C # (vraiment, n'importe quel code) sans un bon profileur. En fait, cela aide considérablement d'avoir à la fois un profileur d'échantillonnage et de traçage.
Sans un bon profileur, vous risquez de créer de fausses optimisations et, surtout, d'optimiser des routines qui ne sont pas un problème de performances en premier lieu.
Les trois premières étapes du profilage doivent toujours être 1) Mesurer, 2) Mesurer, puis 3) Mesurer ....
Consignes d'optimisation:
Comme les processeurs continuent de s'accélérer, le principal goulot d'étranglement dans la plupart des applications n'est pas le processeur, mais la bande passante: la bande passante vers la mémoire hors puce, la bande passante vers le disque et la bande passante vers le réseau.
Commencez à l'extrémité: utilisez YSlow pour voir pourquoi votre site Web est lent pour les utilisateurs finaux, puis reculez et corrigez vos accès à la base de données pour qu'ils ne soient pas trop larges (colonnes) et pas trop profonds (lignes).
Dans les très rares cas où cela vaut la peine de faire quoi que ce soit pour optimiser l'utilisation du processeur, veillez à ne pas avoir d'impact négatif sur l'utilisation de la mémoire: j'ai vu des `` optimisations '' où les développeurs ont essayé d'utiliser la mémoire pour mettre en cache les résultats pour économiser les cycles du processeur. L'effet net a été de réduire la mémoire disponible pour mettre en cache les pages et les résultats de la base de données, ce qui a rendu l'application beaucoup plus lente! (Voir règle sur la mesure.)
J'ai également vu des cas où un algorithme non optimisé "stupide" a battu un algorithme optimisé "intelligent". Ne sous-estimez jamais à quel point les compilateurs et les concepteurs de puces sont devenus de bons transformateurs de code en boucle `` inefficace '' en code super efficace qui peut s'exécuter entièrement dans la mémoire sur puce avec pipelining. Votre algorithme "intelligent" basé sur un arbre avec une boucle intérieure non bouclée comptant en arrière que vous pensiez être "efficace" peut être battu simplement parce qu'il n'a pas réussi à rester dans la mémoire sur puce pendant l'exécution. (Voir règle sur la mesure.)
Lorsque vous travaillez avec des ORM, tenez compte des sélections N + 1.
List<Order> _orders = _repository.GetOrders(DateTime.Now);
foreach(var order in _orders)
{
Print(order.Customer.Name);
}
Si les clients ne sont pas chargés avec impatience, cela pourrait entraîner plusieurs allers-retours dans la base de données.
Si vous identifiez une méthode comme un goulot d'étranglement, mais vous ne savez pas quoi faire à ce sujet , vous êtes essentiellement coincé.
Je vais donc énumérer quelques éléments. Toutes ces choses ne sont pas des balles d'argent et vous devrez toujours profiler votre code. Je fais juste des suggestions pour ce que vous pourriez faire et pouvez parfois aider. Surtout les trois premiers sont importants.
OK, je dois ajouter mon préféré: si la tâche est assez longue pour une interaction humaine, utilisez une pause manuelle dans le débogueur.
Contre. un profileur, cela vous donne une pile d'appels et des valeurs variables que vous pouvez utiliser pour vraiment comprendre ce qui se passe.
Faites-le 10 à 20 fois et vous aurez une bonne idée de quelle optimisation pourrait vraiment faire la différence.
Les gens ont des idées amusantes sur ce qui compte vraiment. Le débordement de pile est plein de questions sur, par exemple, est ++i
plus "performant" que i++
. Voici un exemple de réglage des performances réelles , et c'est fondamentalement la même procédure pour n'importe quelle langue. Si le code est simplement écrit d'une certaine manière "parce qu'il est plus rapide", c'est une supposition.
Bien sûr, vous n'écrivez pas délibérément du code stupide, mais si la supposition fonctionnait, il n'y aurait pas besoin de profileurs et de techniques de profilage.
La vérité est qu'il n'existe pas de code optimisé parfait. Vous pouvez cependant optimiser pour une portion spécifique de code, sur un système connu (ou un ensemble de systèmes) sur un type de CPU (et un nombre) connus , une plateforme connue (Microsoft? Mono ?), un framework connu/ BCL version, une version CLI connue, un compilateur connu version (bogues, modifications de spécifications, modifications), une quantité connue de mémoire totale et disponible, une origine d'assembly connue ( GAC ? disk? remote?), avec activité connue du système d'arrière-plan provenant d'autres processus.
Dans le monde réel, utilisez un profileur et examinez les bits importants; généralement les choses évidentes sont tout ce qui concerne les E/S, tout ce qui implique le threading (encore une fois, cela change énormément entre les versions) et tout ce qui implique des boucles et des recherches, mais vous pourriez être surpris de voir quel code "évidemment mauvais" n'est pas réellement un problème, et quel code "évidemment bon" est un énorme coupable.
Dites au compilateur quoi de faire, pas comment de le faire. Par exemple, foreach (var item in list)
est meilleure que for (int i = 0; i < list.Count; i++)
et m = list.Max(i => i.value);
est meilleure que list.Sort(i => i.value); m = list[list.Count - 1];
.
En disant au système ce que vous voulez faire, il peut déterminer la meilleure façon de le faire. LINQ est bon car ses résultats ne sont calculés que lorsque vous en avez besoin. Si vous n'utilisez que le premier résultat, il n'a pas à calculer le reste.
En fin de compte (et cela s'applique à toute la programmation), minimisez les boucles et minimisez ce que vous faites dans les boucles. Encore plus important est de minimiser le nombre de boucles à l'intérieur de vos boucles. Quelle est la différence entre un algorithme O(n) et un algorithme O (n ^ 2)? L'algorithme O (n ^ 2) a une boucle à l'intérieur d'une boucle.
Je n'essaie pas vraiment d'optimiser mon code, mais parfois je vais passer en revue et utiliser quelque chose comme le réflecteur pour remettre mes programmes à la source. Il est intéressant de comparer ensuite ce que je me trompe avec ce que le réflecteur produira. Parfois, je trouve que ce que j'ai fait sous une forme plus compliquée a été simplifié. Peut ne pas optimiser les choses mais m'aide à voir des solutions plus simples aux problèmes.