web-dev-qa-db-fra.com

Conseils pour optimiser les programmes C # /. NET

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:

  • Lors de la concaténation de plusieurs chaînes, utilisez à la place StringBuilder. Voir le lien en bas pour les mises en garde à ce sujet.
  • Utilisez 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 .

77
Bob

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:

  • Fixez-vous des objectifs significatifs, mesurables et axés sur le client.
  • Créez des suites de tests pour tester vos performances par rapport à ces objectifs dans des conditions réalistes mais contrôlées et reproductibles.
  • Si ces suites montrent que vous n'atteignez pas vos objectifs, utilisez des outils tels que des profileurs pour comprendre pourquoi.
  • Optimisez ce que le profileur identifie comme le sous-système le moins performant. Gardez le profilage à chaque changement afin de bien comprendre l'impact sur les performances de chacun.
  • Répétez jusqu'à ce qu'une des trois choses se produise (1) vous atteignez vos objectifs et expédiez le logiciel, (2) vous révisez vos objectifs vers le bas en quelque chose que vous pouvez réaliser, ou (3) votre projet soit annulé parce que vous n'avez pas pu atteindre vos objectifs.

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.

106
Eric Lippert

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 ....

45
Reed Copsey

Consignes d'optimisation:

  1. Ne le faites pas sauf si vous en avez besoin
  2. Ne le faites pas s'il est moins cher de jeter du nouveau matériel sur le problème au lieu d'un développeur
  3. Ne le faites que si vous pouvez mesurer les changements dans un environnement équivalent à la production
  4. Ne le faites que si vous savez utiliser un processeur et un profileur de mémoire
  5. Ne le faites pas si cela rend votre code illisible ou impossible à maintenir

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.)

21
Ian Mercer

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.

16
Aaron
  • N'utilisez pas de nombres magiques, utilisez des énumérations
  • Ne codez pas les valeurs
  • Utilisez des génériques dans la mesure du possible, car il est sûr et évite la boxe et le déballage
  • Utilisez un gestionnaire d'erreurs là où c'est absolument nécessaire
  • Éliminer, éliminer, éliminer. CLR ne sait pas comment fermer vos connexions à la base de données, fermez-les donc après utilisation et éliminez les ressources non gérées
  • Utiliser le bon sens!
13
SoftwareGeek

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.

  • Essayez de résoudre le problème en utilisant simplement (ou: principalement) des types ou des tableaux de bas niveau.
  • Les problèmes sont souvent mineurs - l'utilisation d'un algorithme intelligent mais complexe ne vous fait pas toujours gagner, surtout si l'algorithme moins intelligent peut être exprimé dans du code qui utilise uniquement (des tableaux de) types de bas niveau. Prenons par exemple InsertionSort vs MergeSort pour n <= 100 ou l'algorithme de recherche Dominator de Tarjan vs l'utilisation de vecteurs de bits pour résoudre naïvement la forme de flux de données du problème pour n <= 100. (le 100 est bien sûr juste pour vous donner une idée - profile!)
  • Envisagez d'écrire un cas spécial qui peut être résolu en utilisant uniquement des types de bas niveau (souvent des instances de problème de taille <64), même si vous devez conserver l'autre code pour les instances de problème plus importantes.
  • Apprenez l'arithmétique au niveau du bit pour vous aider avec les deux idées ci-dessus.
  • BitArray peut être votre ami, par rapport à Dictionary, ou pire, List. Mais attention, l'implémentation n'est pas optimale; Vous pouvez écrire vous-même une version plus rapide. Au lieu de tester que vos arguments sont hors de portée, etc., vous pouvez souvent structurer votre algorithme de sorte que l'index ne puisse pas sortir de toute façon - mais vous ne pouvez pas supprimer la vérification du BitArray standard et ce n'est pas gratuit .
  • Comme exemple de ce que vous pouvez faire avec seulement des tableaux de types de bas niveau, BitMatrix est une structure assez puissante qui peut être implémentée comme juste un tableau d'ulongs et vous pouvez même le parcourir en utilisant un ulong comme "front" parce que vous pouvez prendre le bit d'ordre le plus bas en temps constant (par rapport à la file d'attente dans Breadth First Search - mais évidemment l'ordre est différent et dépend du index des articles plutôt que purement dans l'ordre dans lequel vous les trouvez).
  • La division et le modulo sont vraiment lents à moins que le côté droit ne soit constant.
  • Les mathématiques en virgule flottante sont pas en général plus lentes que les mathématiques entières (plus "quelque chose que vous pouvez faire", mais "quelque chose que vous pouvez ignorer")
  • Le branchement n'est pas gratuit . Si vous pouvez l'éviter en utilisant une simple arithmétique (tout sauf la division ou le modulo), vous pouvez parfois gagner en performances. Déplacer une branche vers l'extérieur d'une boucle est presque toujours une bonne idée.
9
harold

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.

9
Conrad Albrecht

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.

8
Mike Dunlavey

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.

6
Marc Gravell

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.

5
Gabe

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.

2
Aaron Havens