Est-il possible de remplacer un code optimisé par un code lisible?


78

Parfois, vous rencontrez une situation dans laquelle vous devez étendre / améliorer du code existant. Vous voyez que l'ancien code est très maigre, mais il est également difficile à étendre et prend du temps à lire.

Est-ce une bonne idée de le remplacer par du code moderne?

Il y a quelque temps, j'aimais l'approche Lean, mais maintenant, il me semble qu'il vaut mieux sacrifier de nombreuses optimisations au profit d'abstractions plus élevées, de meilleures interfaces et d'un code plus lisible et extensible.

Les compilateurs semblent également s'améliorer. Ainsi, des choses comme par exemple struct abc = {}sont transformées silencieusement en memsets, shared_ptrs produisent à peu près le même code que le twiddling au pointeur brut, les modèles fonctionnent très bien parce qu'ils produisent du code très maigre, etc.

Néanmoins, vous voyez parfois des tableaux basés sur des piles et de vieilles fonctions C avec une logique obscure, et ils ne sont généralement pas sur le chemin critique.

Est-ce une bonne idée de changer ce code si vous devez en toucher un petit morceau de toute façon?


20
La lisibilité et les optimisations ne sont pas opposées la plupart du temps.
deadalnix

23
La lisibilité peut-elle s'améliorer avec certains commentaires?
YetAnotherUser

17
Il est inquiétant que la POO-ification soit considérée comme un «code moderne»
James

7
comme la philosophie slackware: si ce n'est pas cassé ne le répare pas, au moins vous avez une très, très bonne raison de le faire
osdamv

5
Par code optimisé, voulez-vous dire code optimisé réel , ou code dit optimisé?
dan04

Réponses:


115

Où?

  • Sur une page d'accueil d'un site Web à l'échelle de Google, cela n'est pas acceptable. Gardez les choses aussi vite que possible.

  • Dans une partie d'une application utilisée par une personne une fois par an, il est parfaitement acceptable de sacrifier les performances pour gagner en lisibilité du code.

En général, quelles sont les exigences non fonctionnelles pour la partie du code sur laquelle vous travaillez? Si une action doit être exécutée sous 900 ms. dans un contexte donné (machine, charge, etc.) 80% du temps, et en réalité, il exécute moins de 200 ms. Bien sûr, cela rend le code plus lisible 100% du temps, même si cela peut avoir un impact négatif sur les performances. Si par contre la même action ne s'est jamais exécutée en moins de dix secondes, eh bien, vous devriez plutôt essayer de voir ce qui ne va pas avec la performance (ou l'exigence en premier lieu).

En outre, en quoi l'amélioration de la lisibilité diminuera les performances? Souvent, les développeurs adaptent le comportement à une optimisation prématurée: ils craignent d’augmenter la lisibilité, estimant que cela va détruire radicalement les performances, alors que le code plus lisible nécessitera quelques microsecondes de plus pour effectuer la même action.


47
+1! Si vous n'avez pas de chiffres, procurez-vous des chiffres. Si vous n’avez pas le temps d’obtenir des chiffres, vous n’avez pas le temps de le changer.
Tacroy

49
Souvent, les développeurs "optimisent" en se basant sur des mythes et des incompréhensions, par exemple en supposant que "C" est plus rapide que "C ++" et en évitant les fonctionnalités C ++ par le sentiment général que les choses sont plus rapides sans chiffres pour les sauvegarder. Cela me rappelle un développeur C que j'ai suivi et qui pensait que gotoc'était plus rapide que pour les boucles. Ironiquement, l'optimiseur fonctionnait mieux avec les boucles for, il a donc rendu le code à la fois plus lent et plus difficile à lire.
Gort le robot

6
Plutôt que d'ajouter une autre réponse, j'ai +1 cette réponse. Si la compréhension des fragments de code est importante, commentez-les bien. J'ai travaillé dans un environnement C / C ++ / Assembly avec un code hérité vieux de 10 ans avec des dizaines de contributeurs. Si le code fonctionne, laissez-le tranquille et retournez au travail.
Chris K

C'est pourquoi j'ai tendance à n'écrire que du code lisible. La performance peut être atteinte en coupant les quelques points chauds.
Luca

36

Habituellement non .

La modification du code peut entraîner des problèmes inattendus dans le système (ce qui peut parfois passer inaperçu bien plus tard dans un projet si vous ne disposez pas de tests unitaires et de test de fumée en place). Je passe habituellement par la mentalité "si ce n'est pas cassé, ne le répare pas".

L'exception à cette règle est si vous implémentez une nouvelle fonctionnalité qui touche ce code. Si, à ce stade, cela n'a pas de sens et que la refactorisation doit vraiment avoir lieu, allez-y tant que le temps de refactorisation (et que suffisamment d'essais et de solutions de sécurité pour traiter les problèmes de contrefaçon) est pris en compte dans les estimations.

Bien sûr, profil, profil, profil , surtout s'il s'agit d'un domaine de chemin critique.


2
Oui, mais vous supposez que l'optimisation était nécessaire. Nous ne savons pas toujours si c'est le cas et nous voulons probablement déterminer cela en premier.
haylem

2
@haylem: Non, je suppose que le code fonctionne tel quel . Je suppose également que la refactorisation du code causera invariablement des problèmes d’implication ailleurs dans le système (à moins que vous n’ayez affaire à un morceau de code trivial ne dépourvu de dépendances externes).
Demian Brecht

Cette réponse contient une part de vérité, et paradoxalement, c'est parce que les développeurs sont rarement documentés, compris, communiqués ou même pris en compte. Si les développeurs ont une compréhension plus profonde des problèmes rencontrés dans le passé, ils sauront quoi mesurer et seront plus confiants dans les modifications de code.
Rwong

29

En bref: ça dépend

  • Allez-vous vraiment avoir besoin ou utiliser votre version refactorisée / améliorée?

    • Y a-t-il un gain concret, immédiat ou à long terme?
    • Est-ce que ce gain est uniquement pour la maintenabilité, ou vraiment architectural?
  • A-t-il vraiment besoin d'être optimisé?

    • Pourquoi?
    • Quel objectif cible devez-vous viser?

En détails

Allez-vous avoir besoin de trucs nettoyés et brillants?

Il y a des choses à être prudent ici, et vous devez identifier la limite entre ce qui est réel, un gain mesurable et ce qui est juste votre préférence personnelle et votre mauvaise habitude de toucher au code qui ne devrait pas être.

Plus précisément, sachez ceci:

Il y a une telle chose comme sur-ingénierie

C'est un anti-modèle, et il y a des problèmes intégrés:

  • il peut être plus extensible , mais il peut ne pas être plus facile d'étendre,
  • il peut ne pas être plus simple à comprendre ,
  • Dernier point, mais pas le moindre ici: vous pourriez ralentir tout le code.

Certains pourraient aussi citer le principe KISS comme référence, mais ici, c'est contre-intuitif: la voie optimisée est-elle simple ou la voie propre? La réponse n'est pas nécessairement absolue, comme expliqué dans le reste ci-dessous.

Vous n'en aurez pas besoin

Le principe de YAGNI n’est pas complètement orthogonal à l’autre problème, mais il est utile de se poser la question suivante: allez-vous en avoir besoin?

L'architecture plus complexe présente-t-elle vraiment un avantage pour vous, en plus de donner l'impression d'être plus facile à maintenir?

Si ce n'est pas cassé, ne le répare pas

Ecrivez ceci sur une grande affiche et accrochez-le à côté de votre écran ou dans la cuisine au travail ou dans la salle de réunion du développeur. Bien sûr, il y a beaucoup d'autres mantras qui méritent d'être répétés, mais celui-ci est important lorsque vous essayez d'effectuer un "travail de maintenance" et que vous ressentez le besoin de "l'améliorer".

Il est naturel que nous voulions «améliorer» le code ou même simplement le toucher, même inconsciemment, en le lisant pour essayer de le comprendre. C'est une bonne chose, car cela signifie que nous avons une opinion et que nous essayons de mieux comprendre les internes, mais cela dépend également de notre niveau de compétence, de nos connaissances (comment décidez-vous de ce qui est meilleur ou non? Eh bien, voir les sections ci-dessous ...), et toutes les hypothèses que nous faisons sur ce que nous pensons connaître le logiciel ...:

  • fait réellement,
  • a réellement besoin de faire,
  • aura finalement besoin de faire,
  • et comme il le fait bien.

A-t-il vraiment besoin d'être optimisé?

Tout cela étant dit, pourquoi a-t-il été "optimisé"? Ils disent que l' optimisation prématurée est la racine de tous les maux, et si vous voyez un code non documenté et apparemment optimisé, vous pouvez généralement supposer qu'il n'a probablement pas suivi les règles d'optimisation n'a pas besoin chèrement l'effort d'optimisation et qu'il était le L'orgueil du développeur habituel entre en jeu. Encore une fois, c'est peut-être juste à vous de parler maintenant.

Si c'est le cas, dans quelles limites devient-il acceptable? Si vous en avez besoin, cette limite existe et vous donne la possibilité d'améliorer les choses, ou une ligne dure pour décider de laisser tomber.

Aussi, méfiez-vous des caractéristiques invisibles. Il est fort probable que votre version "extensible" de ce code vous permettra de disposer de plus de mémoire au moment de l'exécution et de présenter une empreinte mémoire statique encore plus grande pour l'exécutable. Les fonctionnalités OO brillantes entraînent des coûts non intuitifs comme ceux-ci et peuvent avoir une incidence sur votre programme et l'environnement sur lequel il est supposé fonctionner.

Mesurer, mesurer, mesurer

En tant que Google, tout est une question de données! Si vous pouvez le sauvegarder avec des données, alors c'est nécessaire.

Il n’ya pas si vieux discours que pour chaque dollar dépensé en développement, il sera suivi d’ au moins un dollar en tests et d’ au moins un dollar en support (mais en réalité, c’est beaucoup plus).

Le changement affecte beaucoup de choses:

  • vous devrez peut-être produire une nouvelle version;
  • vous devriez écrire de nouveaux tests unitaires (certainement s'il n'y en avait pas, et votre architecture plus extensible laissera probablement de la place pour plus, car vous avez plus de surface pour les bugs);
  • vous devez écrire de nouveaux tests de performances (pour vous assurer que cela reste stable dans le futur et voir où se trouvent les goulots d'étranglement), et que ces tâches sont délicates à réaliser ;
  • vous aurez besoin de le documenter (et plus extensible signifie plus de place pour les détails);
  • vous (ou quelqu'un d'autre) devrez le tester à nouveau de manière approfondie en assurance qualité;
  • le code n'est (presque) jamais exempt de bogues et vous devrez le prendre en charge.

Il ne faut donc pas mesurer ici uniquement la consommation de ressources matérielles (vitesse d'exécution ou empreinte mémoire), mais également la consommation de ressources de l'équipe . Les deux doivent être prédits pour définir un objectif, pour être mesurés, comptabilisés et adaptés en fonction du développement.

Et pour vous, manager, cela signifie que cela rentre dans le plan de développement actuel, alors communiquez à ce sujet et n’entrez pas dans le code furieux de cow-boy / sous-marin / black-ops.


En général...

Oui mais...

Ne vous méprenez pas, en général, je serais en faveur de ce que vous suggérez, et je le préconise souvent. Mais vous devez être conscient du coût à long terme.

Dans un monde parfait, c'est la bonne solution:

  • le matériel informatique s'améliore avec le temps,
  • les compilateurs et les plates-formes d'exécution s'améliorent avec le temps,
  • vous obtenez un code proche de la perfection, propre, maintenable et lisible.

En pratique:

  • vous pouvez aggraver

    Vous avez besoin de plus de globes oculaires pour le regarder, et plus vous le complexifiez, plus vous avez besoin de globes oculaires.

  • vous ne pouvez pas prédire l'avenir

    Vous ne pouvez pas savoir avec une certitude absolue si vous en aurez jamais besoin et même si les "extensions" dont vous aurez besoin auraient été plus faciles et plus rapides à mettre en œuvre dans l'ancien formulaire, et si elles auraient besoin d'être super optimisées. .

  • Du point de vue de la direction, cela représente un coût énorme sans aucun gain direct.

Faites-en partie du processus

Vous mentionnez ici qu'il s'agit d'un changement relativement modeste et que vous avez des problèmes spécifiques à l'esprit. Je dirais que c'est généralement OK dans ce cas, mais la plupart d'entre nous ont aussi des histoires personnelles de petits changements, des retouches presque chirurgicales, qui ont fini par devenir un cauchemar d'entretien et des délais presque manqués ou dépassés parce que Joe Programmer n'en a pas vu. des raisons derrière le code et touché quelque chose qui n'aurait pas dû être.

Si vous avez un processus pour gérer de telles décisions, vous en prenez l'avantage personnel:

  • Si vous testez les choses correctement, vous saurez plus rapidement si les choses sont cassées,
  • Si vous les mesurez, vous saurez si elles se sont améliorées,
  • Si vous l'examinez, vous saurez si cela jettera les gens.

La couverture des tests, le profilage et la collecte de données sont difficiles

Mais, bien sûr, votre code de test et vos métriques peuvent souffrir des mêmes problèmes que vous essayez d'éviter pour votre code réel: testez-vous les bonnes choses, et sont-elles la bonne pour l'avenir, et mesurez-vous le bon des choses?

Néanmoins, en général, plus vous testez (jusqu’à une certaine limite) et mesurez, plus vous collectez de données et plus vous êtes en sécurité. Mauvaise analogie: pensez-y à la conduite (ou à la vie en général): vous pouvez être le meilleur pilote du monde, si la voiture tombe en panne ou si quelqu'un décide de se suicider en conduisant dans sa voiture avec le leur les compétences pourraient ne pas suffire. Des facteurs environnementaux peuvent vous frapper et les erreurs humaines importent également.

Les revues de code sont les tests de couloir de l'équipe de développement

Et je pense que la dernière partie est la clé ici: faire des revues de code. Vous ne saurez pas la valeur de vos améliorations si vous les faites en solo. Les revues de code sont nos "tests de couloir": suivez la version de Raymond de la loi de Linus, à la fois pour détecter les bugs et pour détecter les sur-techniques et autres anti-patterns, et pour vous assurer que le code correspond aux capacités de votre équipe. Il est inutile d'avoir le "meilleur" code si personne d'autre que vous ne pouvez le comprendre et le maintenir, et cela vaut à la fois pour les optimisations cryptiques et les conceptions architecturales profondes à 6 couches.

En guise de conclusion, rappelez-vous:

Tout le monde sait que déboguer est deux fois plus difficile que d'écrire un programme. Donc, si vous êtes aussi intelligent que vous le pouvez lorsque vous l'écrivez, comment allez-vous le déboguer? - Brian Kernighan


"Si ce n'est pas cassé, ne le corrigez pas" va à l'encontre du refactoring. Peu importe si quelque chose fonctionne, si ce n'est pas maintenable, doit être changé.
Miyamoto Akira

@ Myamoto Akira: c'est une chose à deux vitesses. Si ce n'est pas cassé mais acceptable et moins susceptible de recevoir un soutien, il pourrait être acceptable de le laisser tranquille plutôt que d'introduire de nouveaux bogues potentiels ou de passer du temps de développement sur celui-ci. Il s’agit d’évaluer les avantages, à court et à long terme, de la refactorisation. Il n'y a pas de réponse claire, cela nécessite une évaluation.
Hayem

D'accord. Je suppose que je n'aime pas la phrase (et la philosophie qui la sous-tend) car je considère la refactorisation comme l'option par défaut, et ce n'est que si cela semble trop long ou trop difficile qu'il sera / devrait être décidé de ne pas va avec. Remarquez que j'ai été brûlé par des gens qui ne changeaient pas de choses et qui, bien que fonctionnant, étaient clairement la mauvaise solution dès que vous deviez les entretenir ou les étendre.
Miyamoto Akira

@MiyamotoAkira: les phrases courtes et les déclarations d'opinions ne peuvent pas exprimer grand chose. Ils sont censés être dans votre visage et être développés sur le côté, je suppose. Je suis moi-même un facteur important de révision et de modification du code aussi souvent que possible, même si souvent sans grand filet de sécurité ou sans grande raison. Si c'est sale, vous le nettoyez. Mais, pareillement, je me suis aussi fait brûler plusieurs fois. Et sera toujours brûlé. Tant que ce ne sont pas ceux du 3ème degré, cela ne me dérange pas tellement, jusqu'à présent, c'était toujours des brûlures à court terme pour des gains à long terme.
Hayem

8

En général, vous devez vous concentrer d'abord sur la lisibilité, puis sur les performances beaucoup plus tard. La plupart du temps, ces optimisations de performances sont négligeables, mais les coûts de maintenance peuvent être énormes.

Certes, toutes les "petites" choses devraient être changées au profit de la clarté puisque, comme vous l'avez souligné, la plupart d'entre elles seront optimisées par le compilateur.

En ce qui concerne les optimisations plus larges, il est possible que les optimisations soient réellement essentielles pour atteindre des performances raisonnables (bien que ce ne soit pas le cas étonnamment souvent). Je voudrais faire vos modifications puis profiler le code avant et après les modifications. Si le nouveau code présente des problèmes de performances importants, vous pouvez toujours revenir à la version optimisée, sinon vous pouvez vous en tenir à la version de code plus propre.

Ne modifiez qu'une partie du code à la fois et voyez comment il affecte les performances après chaque cycle de refactoring.


8

Cela dépend de la raison pour laquelle le code a été optimisé, de l'impact de sa modification et de l'impact éventuel du code sur les performances globales. Cela dépend aussi de votre capacité à charger les modifications de test.

Vous ne devez pas effectuer cette modification sans profilage avant et après et, idéalement, sous une charge similaire à celle que l’on verrait en production. Cela signifie ne pas utiliser un sous-ensemble de données sur une machine de développement ou effectuer des tests lorsqu'un seul utilisateur utilise le système.

Si l'optimisation était récente, vous pourrez peut-être contacter le développeur pour savoir exactement quel était le problème et quelle était la lenteur de l'application avant l'optimisation. Cela peut vous en dire beaucoup sur la rentabilité de l'optimisation et sur les conditions dans lesquelles l'optimisation était nécessaire (un rapport couvrant une année entière, par exemple, peut ne pas être devenu lent avant septembre ou octobre, si vous testez votre modification. en février, la lenteur pourrait ne pas encore être apparente et le test invalide).

Si l'optimisation est plutôt ancienne, les méthodes les plus récentes pourraient même être plus rapides et plus lisibles.

En fin de compte, c'est une question pour votre patron. Il faut beaucoup de temps pour refactoriser quelque chose qui a été optimisé et pour s'assurer que le changement n'affecte pas le résultat final et qu'il fonctionne aussi bien ou du moins de manière acceptable par rapport à l'ancien. Il voudra peut-être que vous passiez votre temps dans d’autres domaines au lieu d’assumer une tâche à haut risque afin de gagner quelques minutes de temps de programmation. Il peut également convenir que le code est difficile à comprendre et qu’il a eu besoin d’interventions fréquentes et que de meilleures méthodes sont maintenant disponibles.


6

si le profilage montre que l'optimisation n'est pas nécessaire (ce n'est pas dans une section critique) ou a même une exécution pire (résultat d'une mauvaise optimisation prématurée), alors remplacez-le avec du code lisible qui est plus facile à gérer

assurez-vous également que le code se comporte de la même manière avec les tests appropriés


5

Pensez-y du point de vue des entreprises. Quels sont les coûts du changement? Combien de temps avez-vous besoin pour effectuer le changement et combien économiserez-vous à long terme en rendant le code plus facile à étendre ou à maintenir? Attachez maintenant une étiquette de prix à cette période et comparez-la à l’argent perdu en réduisant les performances. Peut-être devez-vous ajouter ou mettre à niveau un serveur pour compenser la perte de performances. Peut-être que le produit ne répond plus aux exigences et ne peut plus être vendu. Peut-être qu'il n'y a pas de perte. Peut-être que le changement augmente la robustesse et fait gagner du temps ailleurs. Maintenant, prenez votre décision.

Par ailleurs, dans certains cas, il peut être possible de conserver les deux versions d’un fragment. Vous pouvez écrire un test générant des valeurs d'entrée aléatoires et vérifier les résultats avec l'autre version. Utilisez la solution "intelligente" pour vérifier le résultat d'une solution parfaitement compréhensible et évidemment correcte, et vous rassurez ainsi (sans aucune preuve) le fait que la nouvelle solution est équivalente à l'ancienne. Ou bien faites l'inverse et vérifiez le résultat du code compliqué avec le code verbal et documentez ainsi l'intention derrière le piratage de manière non ambiguë.


4

Fondamentalement, vous demandez si la refactorisation est une entreprise rentable. La réponse à cette question est très certainement oui.

Mais...

... vous devez le faire avec soin. Vous avez besoin de tests unitaires, d'intégration, fonctionnels et de performances solides pour tout code que vous refactoring. Vous devez être sûr qu'ils testent vraiment toutes les fonctionnalités requises. Vous devez avoir la capacité de les exécuter facilement et à plusieurs reprises. Une fois que vous avez cela, vous devriez pouvoir remplacer les composants par de nouveaux composants contenant une fonctionnalité équivalente.

Martin Fowler a écrit le livre à ce sujet.


3

Vous ne devez pas modifier le code de production ou de travail sans raison valable. Le "refactoring" n'est pas une raison suffisante à moins que vous ne puissiez faire votre travail sans ce refactoring. Même si vous corrigez des bogues dans le code difficile lui-même, vous devez prendre le temps de le comprendre et d’apporter le changement le plus petit possible. Si le code est si difficile à comprendre, vous ne pourrez pas le comprendre complètement. Ainsi, toute modification apportée aura des effets secondaires imprévisibles - des bogues, en d’autres termes. Plus le changement est important, plus vous risquez de causer des problèmes.

Il y aurait une exception à cela: si le code incompréhensible avait un ensemble complet de tests unitaires, vous pouvez le refactoriser. Comme je n'ai jamais vu ni entendu parler de code incompréhensible avec des tests unitaires complets, vous écrivez d'abord les tests unitaires, obtenez l'accord des personnes nécessaires pour que ces tests unitaires représentent réellement ce que le code devrait faire, et ALORS effectuez les modifications de code. . Je l'ai fait une ou deux fois. C'est une douleur dans la nuque, et très cher, mais produit finalement de bons résultats.


3

S'il ne s'agit que d'un petit morceau de code qui fait quelque chose de relativement simple d'une manière difficile à comprendre, je décalerais la "compréhension rapide" dans un commentaire étendu et / ou une implémentation alternative inutilisée, comme

#ifdef READABLE_ALT_IMPLEMENTATION

   double x=0;
   for(double n: summands)
     x += n;
   return x;

#else

   auto subsum = [&](int lb, int rb){
          double x=0;
          while(lb<rb)
            x += summands[lb++];
          return x;
        };
   double x_fin=0;
   for(double nsm: par_eval( subsum
                           , partitions(n_threads, 0, summands.size()) ) )
     x_fin += nsm;
   return x_fin;

#endif

3

La réponse est, sans perte de généralité, oui. Ajoutez toujours du code moderne lorsque vous voyez du code difficile à lire et supprimez le code incorrect dans la plupart des cas. J'utilise le processus suivant:

  1. Recherchez le test de performance et les informations de profilage correspondantes. S'il n'y a pas de test de performance, alors ce qui peut être affirmé sans preuve peut être rejeté sans preuve. Affirmez que votre code moderne est plus rapide et supprimez l'ancien code. Si quelqu'un argumente (même vous-même), demandez-lui d'écrire le code de profilage pour prouver lequel est le plus rapide.
  2. Si le code de profilage existe, écrivez quand même le code moderne. Nommez-le quelque chose comme <function>_clean(). Ensuite, "faites la course" de votre code contre le mauvais code. Si votre code est meilleur, supprimez l'ancien code.
  3. Si l'ancien code est plus rapide, laissez-y votre code moderne. Cela sert de bonne documentation pour ce que l’autre code est censé faire, et puisque le code "race" existe, vous pouvez continuer à l’exécuter pour documenter les caractéristiques de performances et les différences entre les deux chemins. Vous pouvez également tester les différences de comportement du code. Il est important de noter que le code moderne battra un jour le code "optimisé", garanti. Vous pouvez ensuite supprimer le code incorrect.

CQFD


3

Si je pouvais enseigner au monde une chose (à propos du logiciel) avant de mourir, je lui apprendrais que "Performance versus X" est un faux dilemme.

Le refactoring est généralement considéré comme un avantage en termes de lisibilité et de fiabilité, mais il peut tout aussi bien prendre en charge l'optimisation. Lorsque vous traitez l'amélioration des performances sous la forme d'une série de modifications, vous pouvez respecter la règle de camping tout en accélérant l'application. En fait, du moins à mon avis, il vous incombe éthiquement de le faire.

Par exemple, l'auteur de cette question a rencontré un morceau de code fou. Si cette personne lisait mon code, elle trouverait que la partie folle compte 3-4 lignes. C'est dans une méthode en soi, et le nom de la méthode et sa description indique CE QUE fait la méthode. La méthode contiendrait 2 à 6 lignes de commentaires en ligne décrivant COMMENT le code fou obtient la bonne réponse, malgré son apparence discutable.

Compartimenté de cette manière, vous êtes libre d’échanger à votre guise les implémentations de cette méthode. En effet, c’est probablement pour cela que j’ai écrit la version folle pour commencer. Vous êtes invités à essayer, ou au moins demander des alternatives. La plupart du temps, vous découvrirez que la mise en œuvre naïve est sensiblement pire (en général, je ne dérange que pour une amélioration de 2 à 10 fois), mais les compilateurs et les bibliothèques changent constamment, et qui sait ce que vous pouvez trouver aujourd'hui qui n'était pas disponible quand la fonction a été écrite?


Dans de nombreux cas, l’un des éléments clés de l’efficacité consiste à faire en sorte qu’un code effectue le plus de travail possible de manière efficace. Ce qui me contrarie avec .NET, c’est qu’il n’existe aucun mécanisme efficace pour, par exemple, copier une partie d’une collection sur une autre. La plupart des collections stockent de grands groupes d’articles consécutifs (si ce n’est pas tout) dans des tableaux. Ainsi, par exemple, la copie des 5 000 derniers articles d’une liste de 50 000 articles devrait se décomposer en quelques opérations de copie en bloc (sinon une seule) plus quelques autres. étapes effectuées au plus une poignée de fois chacun.
Supercat

Malheureusement, même dans les cas où de telles opérations devraient pouvoir être effectuées efficacement, il sera souvent nécessaire de disposer de boucles "volumineuses" pour 5 000 itérations (et dans certains cas 45 000!). Si une opération peut être réduite à des éléments tels que des copies groupées en bloc, celles-ci peuvent être optimisées à des degrés extrêmes, générant un gain majeur. Si chaque itération de boucle doit effectuer une douzaine d'étapes, il est difficile d'optimiser chacune d'elles.
Supercat

2

Ce n'est probablement pas une bonne idée de le toucher - si le code était écrit de cette façon pour des raisons de performances, cela signifierait que le modifier pourrait ramener des problèmes de performances qui avaient été résolus auparavant.

Si vous ne décidez de changer les choses pour être plus lisible et extensible: Avant de faire un changement, référence l'ancien code sous forte charge. Mieux encore, si vous pouvez trouver un ancien document ou un ticket de dépannage décrivant le problème de performances que ce code étrange est censé résoudre. Après avoir effectué vos modifications, exécutez à nouveau les tests de performance. Si ce n'est pas très différent, ou toujours dans les paramètres acceptables, c'est probablement OK.

Il peut arriver que lorsque d'autres parties d'un système changent, ce code optimisé en termes de performances ne nécessite plus d'optimisations aussi lourdes, mais il est impossible de le savoir avec certitude sans tests rigoureux.


1
Un des gars avec qui je travaille maintenant adore optimiser les éléments dans les zones touchées une fois par mois par les utilisateurs, si cela se produit souvent. Cela prend du temps et cause fréquemment d'autres problèmes, car il aime coder et valider, et laisser le contrôle de la qualité ou une autre fonction en aval être testé. : / Pour être juste, il est généralement rapide, rapide et précis, mais ces "optimisations" penny-ante ne font que rendre les choses plus difficiles pour le reste de l'équipe et leur décès permanent serait une bonne chose.
DaveE

@DaveE: Ces optimisations sont-elles appliquées en raison de problèmes de performances réels? Ou ce développeur le fait-t-il simplement parce qu'il le peut? Je suppose que si vous savez que les optimisations n'auront aucun impact, vous pouvez les remplacer en toute sécurité par un code plus lisible, mais je ne ferais confiance qu'à un spécialiste du système.
FrustratedWithFormsDesigner

Ils ont fini parce qu'il peut. En fait, il enregistre généralement quelques cycles, mais lorsque l'interaction de l'utilisateur avec l'élément de programme prend quelques secondes (15 à 300 secondes), il est ridicule de réduire d'un dixième de seconde l'exécution. Surtout quand les gens qui le suivent doivent prendre du temps réel pour comprendre ce qu'il a fait. Il s'agit d'une application PowerBuilder construite à l'origine il y a 16 ans. Par conséquent, compte tenu de la genèse des choses, l'état d'esprit est peut-être compréhensible, mais il refuse de mettre à jour son état d'esprit pour l'adapter à la réalité actuelle.
DaveE

@DaveE: Je pense que je suis plus d'accord avec le gars avec qui vous travaillez qu'avec vous. Si je ne suis pas autorisé à réparer des choses qui sont lentes sans aucune raison, je vais devenir fou. Si je vois une ligne de C ++ qui utilise à plusieurs reprises l'opérateur + pour assembler une chaîne, ou un code qui s'ouvre et lit / dev / urandom à chaque fois dans la boucle simplement parce que quelqu'un a oublié de définir un indicateur, je le répare. En étant fanatique à ce sujet, j'ai réussi à garder la vitesse, alors que d'autres personnes l'auraient laissé glisser une microseconde à la fois.
Zan Lynx

1
Eh bien, nous allons devoir accepter d'être en désaccord. Passer une heure à changer quelque chose pour économiser quelques fractions de seconde au moment de l’exécution d’une fonction qui s’exécute de façon très occasionnelle tout en laissant le code en forme de casse-tête pour les autres développeurs n’est pas correct. Si c'étaient des fonctions qui s'exécutaient de manière répétée dans des parties très sollicitées de l'application, très bien. Mais ce n'est pas le cas que je décris. C’est un code vraiment gratuit qui se débat pour rien d’autre que de dire "j’ai fait cette chose que UserX fait une fois par semaine de manière légèrement plus rapide". En attendant, nous avons un travail payant qu’il faut faire.
DaveE

2

Le problème ici est de distinguer "optimisé" de lisible et extensible, ce que nous considérons comme code optimisé et ce que le compilateur considère comme optimisé sont deux choses différentes. Le code que vous envisagez de modifier ne constitue peut-être pas un goulot d'étranglement. Par conséquent, même si le code est "maigre", il n'est même pas nécessaire de "l'optimiser". Ou, si le code est suffisamment ancien, le compilateur peut effectuer des optimisations pour intégrer des éléments intégrés rendant l'utilisation d'une structure intégrée plus récente plus simple ou plus efficace que l'ancien code.

Et "maigre", le code illisible n'est pas toujours optimisé.

J'avais l'habitude de penser que le code intelligent / lean était un bon code, mais tirant parfois parti de règles obscures du langage blessé plutôt que d'aide à la création de code, j'ai été piqué plus que tout dans un travail intégré lorsque j'essayais de soyez intelligent, car le compilateur transforme votre code intelligent en quelque chose de totalement inutilisable par le matériel intégré.


2

Je ne remplacerai jamais le code optimisé par du code lisible car je ne peux pas compromettre les performances et je choisirai d'utiliser des commentaires appropriés dans chaque section afin que tout le monde puisse comprendre la logique implémentée dans cette section optimisée qui résoudra les deux problèmes.

Par conséquent, le code sera optimisé + les commentaires appropriés le rendront également lisible.

REMARQUE: Vous pouvez rendre un code optimisé lisible à l'aide de commentaires appropriés, mais vous ne pouvez pas en faire un code lisible optimisé.


Je serais fatigué de cette approche car il suffit d'une personne qui édite le code pour oublier de garder le commentaire synchronisé. Tout à coup, chaque critique subséquente quittera la salle en pensant qu'elle exécute X alors qu'elle le fait réellement.
John D

2

Voici un exemple pour voir la différence entre un code simple et un code optimisé: https://stackoverflow.com/a/11227902/1396264

vers la fin de la réponse, il remplace simplement:

if (data[c] >= 128)
    sum += data[c];

avec:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

Pour être juste, je ne sais pas du tout quelle déclaration a été remplacée par if, mais comme le répondeur le dit, certaines opérations au niveau des bits donnent le même résultat (je vais juste le croire sur parole) .

Ceci s’exécute en moins d’un quart du temps initial (11.54s contre 2.5s)


1

La principale question est la suivante: l’optimisation est-elle requise?

Si c'est le cas, vous ne pouvez pas le remplacer par un code plus lent, plus lisible. Vous devrez ajouter des commentaires, etc. pour le rendre plus lisible.

Si le code n'a pas besoin d'être optimisé, il ne devrait pas l'être (au point d'affecter la lisibilité) et vous pouvez le reformater pour le rendre plus lisible.

CEPENDANT - assurez-vous de savoir exactement ce que fait le code et comment le tester complètement avant de commencer à changer les choses. Cela inclut l'utilisation maximale, etc. Si vous n'avez pas à composer un ensemble de cas de test et à les exécuter avant et après, vous n'avez pas le temps de refactoriser.


1

Voici comment je fais les choses: d'abord, je le fais fonctionner en code lisible, puis je l'optimise. Je conserve la source d'origine et documente mes étapes d'optimisation.

Ensuite, lorsque je dois ajouter une fonctionnalité, je retourne à mon code lisible, puis ajoute la fonctionnalité et suis les étapes d'optimisation décrites. Parce que vous avez documenté, il est très rapide et facile de réoptimiser votre code avec la nouvelle fonctionnalité.


0

La lisibilité à mon humble avis est plus importante que le code optimisé car dans la plupart des cas, la micro-optimisation n'en vaut pas la peine.

Article sur les micro-optimisations sans sens :

Comme la plupart d'entre nous, je suis fatiguée de lire des articles de blog sur les micro-optimisations non sensées, telles que le remplacement de print par echo, ++ $ i par $ i ++ ou les guillemets doubles par des guillemets simples. Pourquoi? Parce que 99,999999% du temps, cela n'a aucune importance.

"print" utilise un opcode de plus que "echo" car il renvoie en fait quelque chose. Nous pouvons en conclure que l'écho est plus rapide que l'impression. Mais un code d'opération ne coûte rien, vraiment rien.

J'ai essayé une nouvelle installation de WordPress. Le script s'arrête avant qu'il ne se termine avec une "erreur de bus" sur mon ordinateur portable, mais le nombre d'opcodes était déjà supérieur à 2,3 millions. Assez dit.


0

L'optimisation est relative. Par exemple:

Considérez une classe avec un groupe de membres de BOOL:

// no nitpicking over BOOL vs bool allowed
class Pear {
 ...
 BOOL m_peeled;
 BOOL m_sliced;
 BOOL m_pitted;
 BOOL m_rotten;
 ...
};

Vous pourriez être tenté de convertir les champs BOOL en champs de bits:

class Pear {
 ...
 BOOL m_peeled:1;
 BOOL m_sliced:1;
 BOOL m_pitted:1;
 BOOL m_rotten:1;
 ...
};

Dans la mesure où un objet BOOL est typé en tant que INT (qui sur les plates-formes Windows est un entier signé de 32 bits), il faut seize octets et les regroupe en un. C'est une économie de 93%! Qui pourrait s'en plaindre?

Cette hypothèse:

Dans la mesure où un objet BOOL est typé en tant que INT (qui sur les plates-formes Windows est un entier signé de 32 bits), il faut seize octets et les regroupe en un. C'est une économie de 93%! Qui pourrait s'en plaindre?

mène à:

La conversion d'un objet BOOL en un champ à un bit a permis d'économiser trois octets de données, mais vous a coûté huit octets de code lorsque le membre reçoit une valeur non constante. De même, l'extraction de la valeur devient plus chère.

Quel était

 push [ebx+01Ch]      ; m_sliced
 call _Something@4    ; Something(m_sliced);

devient

 mov  ecx, [ebx+01Ch] ; load bitfield value
 shl  ecx, 30         ; put bit at top
 sar  ecx, 31         ; move down and sign extend
 push ecx
 call _Something@4    ; Something(m_sliced);

La version de bitfield est plus grande de neuf octets.

Asseyons-nous et faisons de l'arithmétique. Supposons que chacun de ces champs de champ de bits est accédé six fois dans votre code, trois fois pour l'écriture et trois fois pour la lecture. La croissance du code coûte environ 100 octets. Ce ne sera pas exactement 102 octets, car l'optimiseur pourra peut-être tirer parti des valeurs déjà présentes dans les registres pour certaines opérations, et les instructions supplémentaires pourraient avoir des coûts cachés en termes de flexibilité réduite des registres. La différence réelle peut être plus, peut-être moins, mais pour un calcul à rebours, appelons-le 100. Entre-temps, les économies de mémoire étaient de 15 octets par classe. Par conséquent, le seuil de rentabilité est sept. Si votre programme crée moins de sept instances de cette classe, le coût du code dépasse les économies de données: Votre optimisation de la mémoire était une désoptimisation de la mémoire.

Références

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.