En répondant à cette question , j'ai commencé à me demander pourquoi autant de développeurs pensent qu'un bon design ne doit pas rendre compte des performances, car cela affecterait la lisibilité et / ou la maintenabilité.
Je crois qu'un bon design prend également en compte les performances au moment de sa rédaction et qu'un bon développeur avec un bon design peut écrire un programme efficace sans nuire à la lisibilité ou à la maintenabilité.
Bien que je reconnaisse qu'il existe des cas extrêmes, pourquoi de nombreux développeurs insistent-ils sur le fait qu'un programme / design efficace conduira à une mauvaise lisibilité et / ou à une maintenabilité médiocre, et par conséquent que la performance ne devrait pas être une considération de conception?
Réponses:
Je pense que de tels points de vue sont généralement des réactions à des tentatives d'optimisation prématurée (micro) , qui sont encore courantes et font généralement plus de mal que de bien. Quand on essaie de contrer de tels points de vue, il est facile de tomber - ou du moins de ressembler à - l'autre extrême.
Il est néanmoins vrai qu'avec le développement énorme des ressources matérielles au cours des dernières décennies, pour la plupart des programmes écrits aujourd'hui, les performances ont cessé d'être un facteur limitant majeur. Bien entendu, il convient de prendre en compte les performances attendues et réalisables au cours de la phase de conception, afin d' identifier les cas où les performances peuvent être (ou être) un problème majeur . Et puis, il est en effet important de concevoir pour la performance dès le début. Cependant, la simplicité, la lisibilité et la facilité de maintenance sont toujours plus importantes . Comme d'autres l'ont noté, le code à performances optimisées est plus complexe, plus difficile à lire et à gérer, et plus sujet aux bogues que la solution la plus simple. Ainsi, tout effort d’optimisation doit être prouvé - et pas seulement cru- apporter de réels avantages, tout en réduisant le moins possible la maintenabilité à long terme du programme. Ainsi, une bonne conception isole les parties complexes, exigeantes en performances, du reste du code , qui reste aussi simple et propre que possible.
la source
Si vous répondez à votre question du côté d’un développeur qui travaille sur du code haute performance, plusieurs éléments doivent être pris en compte dans la conception.
Faites-le bien, faites-le beau, faites-le vite. Dans cet ordre.
la source
contains
, utilisez unHashSet
, pas unArrayList
. La performance n’importe peut-être pas, mais il n’ya aucune raison de ne pas le faire. Exploitez les congruences entre un bon design et de bonnes performances - si vous traitez une collection, essayez de tout faire en un seul passage, ce qui sera probablement à la fois plus lisible et plus rapide (probablement).Si je peux prétendre "emprunter" le joli diagramme de @ greengit et faire un petit ajout:
Nous avons tous été "appris" qu'il existe des courbes de compromis. En outre, nous avons tous supposé que nous étions des programmeurs tellement optimaux que tout programme donné que nous écrivons est si serré qu’il est sur la courbe . Si un programme est sur la courbe, toute amélioration dans une dimension entraîne nécessairement un coût dans l'autre dimension.
D'après mon expérience, les programmes ne s'approchent de la courbe qu'en ajustant, en peaufinant, en martelant, en cirant et en général en "code golf". La plupart des programmes offrent de nombreuses possibilités d'amélioration dans toutes les dimensions. Voici ce que je veux dire.
la source
Précisément parce que les composants logiciels hautement performants sont généralement des ordres de grandeur plus complexes que les autres composants logiciels (toutes choses étant égales par ailleurs).
Même dans ce cas, il n’est pas aussi clair que, si les métriques de performance sont une exigence cruciale, il est impératif que la conception présente une complexité qui permette de répondre à de telles exigences. Le danger est qu'un développeur gaspille un sprint sur une fonctionnalité relativement simple en essayant d'extraire quelques millisecondes supplémentaires de son composant.
Quoi qu'il en soit, la complexité de la conception a une corrélation directe avec la capacité d'un développeur à apprendre rapidement et à se familiariser avec une telle conception. De plus, de nouvelles modifications apportées aux fonctionnalités d'un composant complexe peuvent générer des bugs qui pourraient ne pas être détectés par les tests unitaires. Les conceptions complexes comportent de nombreuses autres facettes et scénarios de test possibles à envisager, ce qui rend l'objectif de couverture à 100% des tests unitaires encore plus réaliste.
Cela dit, il convient de noter qu’un composant logiciel peu performant pourrait ne pas fonctionner du simple fait qu’il était bêtement écrit et inutilement complexe sur la base de l’ignorance de l’auteur original (faire 8 appels de base de données pour créer une seule entité alors qu’un seul , code totalement inutile qui conduit à un seul chemin de code indépendamment de, etc ...) Ces cas sont davantage une question d’amélioration de la qualité du code et d’augmentations de performances dues au refactor et PAS à la conséquence attendue nécessairement.
Si un composant est bien conçu, il sera toujours moins complexe qu'un composant similaire conçu pour la performance (toutes choses étant égales par ailleurs).
la source
Ce n'est pas tellement que ces choses ne peuvent pas coexister. Le problème est que le code de tout le monde est lent, illisible et impossible à maintenir à la première itération. Le reste du temps est consacré à l'amélioration de ce qui est le plus important. Si c'est la performance, alors allez-y. N'écrivez pas un code méchant, mais s'il doit simplement être rapide, faites-le vite. Je crois que performance et propreté ne sont fondamentalement pas corrélées. Le code performant ne provoque pas de code laid. Cependant, si vous passez votre temps à ajuster chaque morceau de code pour qu'il soit rapide, devinez ce que vous n'avez pas passé votre temps à faire? Rendre votre code propre et maintenable.
la source
Comme vous pouvez le voir...
La performance et la lisibilité ne sont donc que faiblement liées - et dans la plupart des cas, il n’ya pas de véritables incitations préférant le premier au dernier. Et je parle ici de langues de haut niveau.
la source
À mon avis, la performance devrait être prise en compte lorsqu'il s'agit d'un problème réel (ou, par exemple, d'une exigence). Si vous ne le faites pas, cela conduit à des microoptimisations, ce qui pourrait conduire à un code plus obscurci, ne serait-ce que pour économiser quelques microsecondes ici et là, ce qui conduirait à un code moins gérable et moins lisible. Au lieu de cela, il convient de se concentrer sur les véritables goulots d'étranglement du système, si nécessaire , et de mettre l'accent sur les performances.
la source
Le problème n’est pas la lisibilité, mais l’efficacité. Si vous savez dès le départ que votre algorithme doit être hautement efficace, ce sera l'un des facteurs que vous utiliserez pour le développer.
La plupart des cas d'utilisation n'ont pas besoin d'un code ultra rapide. Dans de nombreux cas, les interactions IO ou utilisateur entraînent beaucoup plus de retard que l'exécution de votre algorithme. Le fait est que vous ne devriez pas faire tout votre possible pour rendre quelque chose de plus efficace si vous ne savez pas que c'est le goulot d'étranglement.
Optimiser le code pour améliorer les performances le rend souvent plus compliqué, car cela implique généralement de faire les choses de manière intelligente, au lieu des plus intuitives. Un code plus complexe est plus difficile à maintenir et plus difficile à récupérer pour les autres développeurs (les deux coûts sont à prendre en compte). Dans le même temps, les compilateurs sont très efficaces pour optimiser les cas courants. Il est possible que votre tentative d'améliorer un cas courant signifie que le compilateur ne reconnaît plus le modèle et ne peut donc pas vous aider à rendre votre code rapide. Il convient de noter que cela ne signifie pas écrire ce que vous voulez sans souci de performance. Vous ne devriez rien faire qui soit clairement inefficace.
Le but est de ne pas s'inquiéter de petites choses qui pourraient améliorer les choses. Utilisez un profileur et voyez que 1) ce que vous avez maintenant est un problème et 2) ce que vous avez changé en une amélioration.
la source
Je pense que la plupart des programmeurs ont ce sentiment de base simplement parce que la plupart du temps, le code de performance est un code basé sur beaucoup plus d'informations (sur le contexte, la connaissance du matériel, l'architecture globale) que tout autre code dans les applications. La plupart du code n'exprimera que certaines solutions à des problèmes spécifiques encapsulés dans certaines abstractions de manière modulaire (comme des fonctions), ce qui implique de limiter la connaissance du contexte à ce qui entre dans cette encapsulation (comme les paramètres de fonction).
Lorsque vous écrivez pour obtenir de hautes performances, après avoir corrigé les optilisations algorithmiques, vous rentrez dans des détails qui nécessitent beaucoup plus de connaissances sur le contexte. Cela peut naturellement submerger tout programmeur qui ne se sent pas suffisamment concentré pour la tâche.
la source
Parce que le coût du réchauffement climatique (à partir de ces cycles de calcul supplémentaires générés par des centaines de millions d'ordinateurs et de centres de données gigantesques) et la durée de vie médiocre de la batterie (sur les appareils mobiles de l'utilisateur), nécessaires pour exécuter leur code mal optimisé, sont rarement visibles sur la plupart des ordinateurs. les performances du programmeur ou les évaluations par les pairs.
C'est une externalité économique négative, semblable à une forme de pollution ignorée. Ainsi, le rapport coût / bénéfice de la réflexion sur la performance est mentalement biaisé par rapport à la réalité.
Les concepteurs de matériel ont travaillé d'arrache-pied en ajoutant des fonctions d'économie d'énergie et de mise à l'échelle d'horloge aux derniers processeurs. Il appartient aux programmeurs de laisser le matériel tirer parti de ces fonctionnalités plus souvent, en évitant d'induire en erreur chaque cycle d'horloge disponible.
AJOUT: De l’antiquité, le coût d’un ordinateur représentait des millions. L’optimisation du temps de calcul était donc très importante. Ensuite, le coût de développement et de maintenance du code est devenu supérieur à celui des ordinateurs, si bien que l’optimisation est tombée en désuétude par rapport à la productivité du programmeur. Aujourd'hui, toutefois, un autre coût devient supérieur au coût des ordinateurs: le coût d'alimentation et de refroidissement de tous ces centres de données devient maintenant supérieur au coût de tous les processeurs internes.
la source
Je pense que c'est difficile d'atteindre tous les trois. Je pense que deux peuvent être réalisables. Par exemple, je pense qu'il est possible d'atteindre efficacité et lisibilité dans certains cas, mais la maintenabilité peut être difficile avec du code micro-réglé. Le plus efficace sur le code de la planète manquent souvent à la fois la maintenabilité et la lisibilité est probablement comme évident pour la plupart, à moins que vous êtes le genre qui peut comprendre la main-SoA vectorisé, multithread Code SIMD Intel écrit avec l' assemblage inline, ou le plus coupe Algorithmes novateurs utilisés dans l'industrie avec des articles mathématiques de 40 pages publiés il y a à peine 2 mois et 12 bibliothèques d'un code pour une structure de données incroyablement complexe.
Micro-efficacité
Une chose que je suggérerais qui pourrait être contraire à l’opinion populaire est que le code algorithmique le plus intelligent est souvent plus difficile à maintenir que l’algorithme simple le plus micro-réglé. Cette idée que les améliorations en matière d’évolutivité rapportent plus sur le code micro-ajusté (ex: schémas d’accès conviviaux pour le cache, multithreading, SIMD, etc.) est quelque chose que je contesterais, au moins après avoir travaillé dans un secteur extrêmement complexe. structures de données et algorithmes (l'industrie des effets visuels), en particulier dans des domaines tels que le traitement de maillage, parce que le bang est énorme, mais que le coût est extrêmement coûteux lorsque vous introduisez de nouveaux algorithmes et structures de données dont personne n'a jamais entendu parler depuis qu'ils sont de marque Nouveau. De plus, je '
Donc, cette idée que les optimisations algorithmiques ont toujours préséance sur, disons, les optimisations liées aux modèles d'accès à la mémoire est toujours quelque chose avec lequel je n'étais pas tout à fait d'accord. Bien sûr, si vous utilisez une sorte de bulle, aucune micro-optimisation ne peut vous aider… mais dans des limites raisonnables, je ne pense pas que ce soit toujours aussi net. Et on peut soutenir que les optimisations algorithmiques sont plus difficiles à maintenir que les micro-optimisations. Je trouve qu'il est beaucoup plus facile de maintenir, par exemple, Embree d'Intel, qui utilise un algorithme BVH classique et simple et en résout simplement le foutre que le code OpenVDB de Dreamwork pour des méthodes de pointe d'accélération algorithmique de la simulation fluide. Donc, au moins dans mon secteur d'activité, j'aimerais voir davantage de personnes familiarisées avec la micro-optimisation de l'architecture informatique, comme le faisait Intel lorsqu'elles sont entrées en scène, au lieu de proposer des milliers et des milliers de nouveaux algorithmes et structures de données. Avec des micro-optimisations efficaces, les utilisateurs pourraient potentiellement trouver de moins en moins de raisons d'inventer de nouveaux algorithmes.
Auparavant, je travaillais dans une base de code héritée, où presque chaque opération utilisateur possédait sa propre structure de données et son algorithme (ajoutant ainsi des centaines de structures de données exotiques). Et la plupart d’entre eux avaient des caractéristiques de performance très asymétriques, s’appliquant très étroitement. Cela aurait été tellement plus facile si le système pouvait s'articuler autour d'une vingtaine de structures de données plus largement applicables, et je pense que cela aurait pu être le cas si elles avaient été optimisées au niveau micro. Je parle de ce cas parce que la micro-optimisation peut potentiellement améliorer considérablement la maintenabilité dans un tel cas si cela signifie la différence entre des centaines de structures de données micro-pessimisées qui ne peuvent même pas être utilisées en toute sécurité à des fins strictes en lecture seule, ce qui implique des erreurs de cache en mémoire. droit vs.
Langages fonctionnels
Entre-temps, certains des codes les plus faciles à gérer que j'ai rencontrés étaient assez efficaces, mais extrêmement difficiles à lire, car ils étaient écrits dans des langages fonctionnels. En général, la lisibilité et la maintenabilité sont des idées contradictoires à mon avis.
Il est très difficile de rendre le code lisible, maintenable et efficace en même temps. En règle générale, vous devez faire un compromis sur l'un de ces trois, voire deux, comme compromettre la lisibilité pour la maintenabilité ou compromettre la maintenabilité pour l'efficacité. C’est généralement la facilité de maintenance qui en souffre lorsque vous recherchez beaucoup les deux autres.
Lisibilité contre maintenabilité
Maintenant, comme je l’ai dit, la lisibilité et la maintenabilité ne sont pas des concepts harmonieux. Après tout, le plus code lisible pour la plupart d' entre nous mortels cartes très intuitivement à des modèles pensée humaine, et les modèles de pensées humaines sont intrinsèquement sujettes à l' erreur: " . Dans ce cas, le faire si cela se produit, le faire faire autrement cette Oops.. J'ai oublié quelque chose! Si ces systèmes interagissent les uns avec les autres, cela devrait se produire pour que ce système puisse le faire ... oh, attendez, qu'en est-il de ce système lorsque cet événement est déclenché?"J'avais oublié la citation exacte, mais quelqu'un a dit un jour que si Rome était construite comme un logiciel, il suffirait qu'un oiseau se pose sur un mur pour le faire tomber. C'est le cas de la plupart des logiciels. C'est plus fragile que nous nous soucions souvent de Quelques lignes de code apparemment inoffensif ici et là pourraient bien nous permettre de revenir sur notre conception, et les langages de haut niveau visant à être aussi lisibles que possible ne font pas exception à de telles erreurs de conception. .
Les langages fonctionnels purs sont à peu près aussi invulnérables que possible (pas même invulnérables, mais relativement plus proches que la plupart des autres). Et c'est en partie parce qu'ils ne correspondent pas intuitivement à la pensée humaine. Ils ne sont pas lisibles. Ils nous imposent des schémas de pensée qui nous obligent à résoudre des problèmes avec le moins de cas spéciaux possible en utilisant le minimum de connaissances possible et sans provoquer d'effets secondaires. Ils sont extrêmement orthogonaux, ils permettent souvent de changer sans cesse le code, si surprenant que nous devons repenser la conception sur une planche à dessin, au point de changer d’avis sur la conception globale, sans avoir à tout réécrire. Cela ne semble pas être plus facile à gérer que ça ... mais le code est toujours très difficile à lire,
la source
Un problème est que le temps fini imparti aux développeurs signifie que tout ce que vous cherchez à optimiser vous évite de passer du temps sur les autres problèmes.
Il existe une assez bonne expérience sur ce sujet référencé dans Code complet de Meyer. Différents groupes de développeurs ont été sollicités pour optimiser la vitesse, l'utilisation de la mémoire, la lisibilité, la robustesse, etc. Il a été constaté que leurs projets obtenaient de bons résultats dans tout ce qu’on leur demandait d’optimiser, mais moins bien dans toutes les autres qualités.
la source
Parce que les programmeurs expérimentés ont appris que c'était vrai.
Nous avons travaillé avec un code maigre et moyen, sans problèmes de performances.
Nous avons travaillé sur de nombreux codes qui, pour résoudre les problèmes de performances, sont TRÈS complexes.
Un exemple immédiat qui me vient à l’esprit est que mon dernier projet incluait 8 192 tables SQL partagées manuellement. Cela était nécessaire en raison de problèmes de performances. La configuration permettant de sélectionner une table est beaucoup plus simple que de sélectionner et de gérer 8 192 fragments.
la source
Il existe également des morceaux célèbres de code hautement optimisé qui plieront le cerveau de la plupart des gens et qui soutiendront le fait que le code hautement optimisé est difficile à lire et à comprendre.
Voici le plus célèbre je pense. Extrait de Quake III Arena et attribué à John Carmak, bien que je pense qu'il y ait eu plusieurs itérations de cette fonction et qu'elle n'a pas été créée à l'origine par lui ( Wikipedia n'est-il pas génial? ).
la source