Le changement graduel de la méthodologie d’écriture du code a-t-il eu une incidence sur les performances du système? Et devrais-je m'en soucier?

35

TD; DR:

Il y avait une certaine confusion quant à ce que je demandais, voici donc l'idée maîtresse derrière la question:

J'ai toujours voulu que la question soit ce qu'elle est. Je l’ai peut-être mal articulé à l’origine. Mais l'intention a toujours été " est un code modulaire, séparé, en vrac, découplé, refactorisé " nettement plus lentement que par nature " monolithique monolithique, tout faire à la même place, un fichier, étroitement couplé ". Le reste n'est que des détails et diverses manifestations de ce que j'ai découvert à l'époque ou maintenant ou ultérieurement. C'est plus lent sur certaines échelles. Comme un disque non défragmenté, vous devez ramasser les morceaux de partout. C'est plus lent. Pour sûr. Mais devrais-je m'en soucier?

Et la question ne concerne pas ...

pas de micro-optimisation, d'optimisation prématurée, etc. Il ne s'agit pas "d'optimiser telle ou telle partie à mort".

Qu'est-ce que c'est alors?

Il est sur l' ensemble méthodologie et des techniques et des façons de penser à écrire du code qui a émergé au fil du temps:

  • "injecte ce code dans ta classe en tant que dépendance"
  • "écrire un fichier par classe"
  • "séparez votre vue de votre base de données, contrôleur, domaine".
  • n'écrivez pas un bloc de code unique homogène, mais écrivez de nombreux composants modulaires distincts qui fonctionnent ensemble

Il s'agit de la manière et du style de code qui sont actuellement - au cours de cette décennie - vus et préconisés dans la plupart des cadres, préconisés lors de conventions, transmis via la communauté. Nous sommes en train de passer de «blocs monolithiques» à des «microservices». Et avec cela vient le prix en termes de performances et de frais généraux au niveau de la machine, ainsi que certains frais généraux au niveau du programmeur.

La question initiale suit:

Dans le domaine de l’informatique, j’ai constaté un changement notable de la pensée en matière de programmation. Je rencontre assez souvent le conseil qui va comme ceci:

  • écrire du code fonctionnel plus petit (plus testable et maintenable de cette façon)
  • refactorisez le code existant en morceaux de code de plus en plus petits jusqu'à ce que la plupart de vos méthodes / fonctions ne soient plus que de quelques lignes et que leur but soit clair (ce qui crée plus de fonctions par rapport à un bloc monolithique plus grand)
  • écrire des fonctions qui ne font qu'une chose - séparation des problèmes, etc. (ce qui crée généralement plus de fonctions et plus d'images sur une pile)
  • créer plus de fichiers (une classe par fichier, plus de classes à des fins de décomposition, pour des couches telles que MVC, l'architecture de domaine, les modèles de conception, OO, etc., ce qui crée plus d'appels de système de fichiers)

Ceci est un changement par rapport aux pratiques de codage "anciennes" ou "obsolètes" ou "spaghettis" où vous avez des méthodes couvrant 2500 lignes, et de grandes classes et des objets divins faisant tout.

Ma question est la suivante:

lorsqu’il s’appelle au code machine, aux 1 et aux 0, aux instructions d’assemblage, aux plateaux de disque dur, devrais-je être inquiet du fait que mon code OO parfaitement séparé par classe avec une variété de fonctions et de méthodes remodelées de petite à minuscule génère aussi beaucoup de frais supplémentaires?

Détails

Bien que je ne sois pas intimement familiarisé avec la façon dont le code OO et ses appels de méthode sont traités dans ASM, et comment les appels de base de données et les appels de compilateur se traduisent par le déplacement du bras de l'actionneur sur un plateau de disque dur, j'ai une idée. Je suppose que chaque appel de fonction supplémentaire, appel d'objet ou appel "#include" (dans certaines langues) génère un ensemble d'instructions supplémentaire, augmentant ainsi le volume du code et ajoutant divers frais généraux de "câblage de code", sans ajout de code "utile" . J'imagine également que de bonnes optimisations peuvent être effectuées sur ASM avant qu'il ne soit réellement exécuté sur le matériel, mais que cette optimisation ne peut que faire beaucoup aussi.

Par conséquent, ma question est de savoir combien de temps système (en espace et en vitesse) le code bien séparé (code divisé en des centaines de fichiers, classes et modèles de conception, etc.) introduit réellement par rapport à "une grosse méthode qui contient tout dans un fichier monolithique ", en raison de cette surcharge?

UPDATE pour plus de clarté:

Je suppose que prendre le même code et le scinder, le refactoriser, le découpler en de plus en plus de fonctions, d'objets, de méthodes et de classes entraînera de plus en plus de passages de paramètres entre des fragments de code plus petits. Parce que pour sûr, le code de refactoring doit garder le thread, et cela nécessite le passage de paramètre. Des méthodes plus ou plusieurs classes ou plus des modèles de conception Méthodes d' usine, les résultats dans plus généraux de passer plusieurs bits d'information plus que c'est le cas dans une seule classe monolithique ou méthode.

Il a été dit quelque part (citation à déterminer) que jusqu’à 70% de tout le code est constitué de l’instruction MOV de ASM, qui charge les registres de la CPU avec les variables appropriées et non le calcul en cours. Dans mon cas, vous chargez le temps du processeur avec les instructions PUSH / POP afin de fournir des liaisons et des passages de paramètres entre différents morceaux de code. Plus vous réduisez vos morceaux de code, plus le "couplage" est nécessaire. Je crains que ce lien ne vienne alourdir et ralentir les logiciels et je me demande si cela devrait me préoccuper, et à quel point, le cas échéant, car les générations actuelles et futures de programmeurs qui construisent des logiciels pour le siècle prochain , devra vivre et consommer des logiciels construits selon ces pratiques.

UPDATE: plusieurs fichiers

J'écris maintenant un nouveau code qui remplace lentement l'ancien code. En particulier, j'ai remarqué que l'une des anciennes classes était un fichier de ligne d'environ 3 000 lignes (comme mentionné précédemment). À présent, il devient un ensemble de 15 à 20 fichiers situés dans divers répertoires, y compris des fichiers de test et non du framework PHP que j'utilise pour lier certaines choses. D'autres fichiers sont également à venir. En ce qui concerne les E / S de disque, le chargement de plusieurs fichiers est plus lent que le chargement d'un fichier volumineux. Bien sûr, tous les fichiers ne sont pas chargés, ils sont chargés en fonction des besoins et il existe des options de mise en cache disque et de mise en cache mémoire, et pourtant je pense que cela loading multiple filesprend plus de traitement que de loading a single filemémoire. J'ajoute cela à ma préoccupation.

UPDATE: Dependency Inject tout

Revenons à cela après un moment .. Je pense que ma question a été mal comprise. Ou peut-être que j'ai choisi de mal comprendre certaines réponses. Je ne parle pas de micro-optimisation, comme l'ont indiqué certaines réponses (du moins, je pense que qualifier ce que je parle de micro-optimisation est un terme impropre), mais du mouvement du "code du refactor pour desserrer le couplage étroit", dans son ensemble , à tous les niveaux du code. Je viens de Zend Con récemment où ce style de code a été l’un des points centraux et des pièces maîtresses de la convention. Découplez la logique de la vue, la vue du modèle, le modèle de la base de données et, si vous le pouvez, dissociez les données de la base de données. Dépendence-Tout injecter, ce qui signifie parfois simplement ajouter un code de câblage (fonctions, classes, passe-partout) qui ne fait rien, mais sert de point de couture / crochet, doublant facilement la taille du code dans la plupart des cas.

MISE À JOUR 2: La "séparation du code dans plus de fichiers" affecte-t-elle considérablement les performances (à tous les niveaux de l'informatique)

Quel est l' compartmentalize your code into multiple filesimpact de la philosophie de l'informatique actuelle (performances, utilisation du disque, gestion de la mémoire, tâches de traitement de la CPU)?

je parle de

Avant...

Dans un passé pas si lointain hypothétique et pourtant bien réel, vous pourriez facilement écrire un mono-bloc d'un fichier qui contient des modèles et des vues et un contrôleur spaghetti ou non-spaghetti-codé, mais qui exécute tout lorsqu'il est déjà chargé. En faisant quelques tests dans le passé en utilisant du code C, j'ai découvert qu'il est BEAUCOUP plus rapide de charger un fichier de 900 Mo en mémoire et de le traiter en gros morceaux plutôt que de charger un tas de fichiers plus petits et de les traiter dans un repas de paix plus petit. des morceaux faisant le même travail à la fin.

.. Et maintenant*

Aujourd'hui, je me retrouve à regarder du code qui montre un grand livre, qui a des fonctionnalités telles que .. si un article est un "ordre", affichez le bloc HTML. Si un élément de campagne peut être copié, imprimez un bloc HTML comportant une icône et des paramètres HTML derrière lui, vous permettant ainsi de faire la copie. Si l'élément peut être déplacé vers le haut ou le bas, affichez les flèches HTML appropriées. Etc. Je peux, grâce à Zend Framework, créerpartial()appels, ce qui signifie essentiellement "appeler une fonction qui prend vos paramètres et les insère dans un fichier HTML séparé qu'il appelle également". Selon le niveau de détail que je souhaite obtenir, je peux créer des fonctions HTML distinctes pour les plus petites parties du registre. Un pour la flèche vers le haut, une flèche vers le bas, un pour "puis-je copier cet élément", etc. Créez facilement plusieurs fichiers simplement pour afficher une petite partie de la page Web. En prenant mon code et le code Zend Framework en coulisse, le système / pile appelle probablement près de 20 à 30 fichiers différents.

Quelle?

Je m'intéresse aux aspects, à l' usure de la machine, qui sont créés en cloisonnant le code dans de nombreux fichiers séparés plus petits.

Par exemple, pour charger plus de fichiers, vous devez les placer à différents endroits du système de fichiers et dans divers emplacements de disques durs physiques, ce qui signifie plus de temps de recherche et de lecture des disques durs.

Pour le processeur, cela signifie probablement plus de changement de contexte et de chargement de différents registres.

Dans ce sous-bloc (mise à jour n ° 2), je m'intéresse plus strictement à l'utilisation de plusieurs fichiers pour effectuer les mêmes tâches pouvant être effectuées dans un seul fichier, qui affecte les performances du système.

Utilisation de l'API Zend Form et du HTML simple

J'ai utilisé l'API Zend Form avec les pratiques OO les plus récentes et les plus modernes, pour construire un formulaire HTML avec validation, transformant celui-ci POSTen objets de domaine.

Il m'a fallu 35 fichiers pour le faire.

35 files = 
    = 10 fieldsets x {programmatic fieldset + fieldset manager + view template} 
    + a few supporting files

Tous ces éléments pourraient être remplacés par quelques fichiers simples HTML + PHP + JS + CSS, soit peut-être un total de 4 fichiers légers.

Est-ce mieux? Est-ce pire? ... Imaginez le chargement de 35 fichiers + de nombreux fichiers de la bibliothèque Zend Zramework qui les font fonctionner, contre 4 fichiers simples.

Dennis
la source
1
Question géniale. Je vais faire des analyses comparatives (prenez-moi un jour ou deux pour trouver de bons cas). Cependant, les améliorations de la vitesse à ce niveau ont un coût énorme en termes de lisibilité et de développement. Ma supposition initiale est que le résultat est des gains de performance négligeables.
Dan Sabin
7
@ Dan: Pouvez-vous s'il vous plaît le mettre dans votre calendrier pour comparer le code après 1, 5 et 10 ans de maintenance. Si je me souviens bien, je vérifierai les résultats :)
mattnz
1
Oui c'est le vrai kicker. Je pense que nous sommes tous d'accord pour dire qu'il faut faire moins de recherches et que les appels de fonction sont plus rapides. Cependant, je ne peux pas imaginer un cas où cela a été préféré à des choses comme la formation facile et durable de nouveaux membres de l’équipe.
Dan Sabin
2
Pour clarifier, avez-vous des exigences de vitesse spécifiques pour votre projet? Ou voulez-vous simplement que votre code "aille plus vite!" Si c’est le dernier cas, je ne me ferais pas de souci, un code suffisamment rapide, mais facile à gérer, est de loin meilleur que le code plus rapide que rapide, mais qui est un gâchis.
Cormac Mulhall
4
L'idée d'éviter les appels de fonction pour des raisons de performance est exactement le genre de pensée démente batshit contre laquelle Dijkstra a critiqué dans sa célèbre citation sur l'optimisation prématurée. Sérieusement, je ne peux pas
RibaldEddie

Réponses:

10

Ma question est la suivante: quand il s'agit d'appeler du code machine, des 1 et des 0, des instructions d'assemblage, devrais-je craindre que mon code séparé par classe avec une variété de fonctions minuscules à minuscules génère trop de temps système supplémentaire?

Ma réponse est oui, vous devriez. Non pas parce que vous avez beaucoup de petites fonctions (jadis, la surcharge des fonctions d’appel était assez importante et vous pouviez ralentir votre programme en faisant un million de petits appels en boucle, mais aujourd’hui les compilateurs les alignent pour vous et le reste est pris entretenez-vous avec les algorithmes de prédiction sophistiqués du CPU, alors ne vous inquiétez pas pour ça) mais parce que vous introduirez trop le concept de superposition dans vos programmes lorsque la fonctionnalité est trop petite pour vous gêner. Si vous avez des composants plus volumineux, vous pouvez être raisonnablement sûr qu'ils n'effectuent pas le même travail encore et encore, mais vous pouvez rendre votre programme si minutieusement détaillé que vous pouvez vous trouver incapable de vraiment comprendre les chemins d'appel, et aboutir à quelque chose cela fonctionne à peine (et est à peine maintenable).

Par exemple, j'ai travaillé à un endroit qui m'a montré un projet de référence pour un service Web avec 1 méthode. Le projet comprenait 32 fichiers .cs - pour un seul service Web! J’imaginais que c’était beaucoup trop complexe, même si chaque partie était minuscule et facile à comprendre en soi. Quand il s’agissait de décrire le système dans son ensemble, je me suis vite retrouvé dans le besoin de retracer les appels pour voir ce qu’il faisait. trop d'abstractions impliquées, comme on peut s'y attendre). Mon service Web de remplacement était constitué de 4 fichiers .cs.

Je n'ai pas mesuré les performances car je pensais que cela aurait été à peu près le même, mais je peux garantir que le mien était nettement moins cher à entretenir. Lorsque tout le monde parle du temps du programmeur comme étant plus important que du temps du processeur, alors créez des monstres complexes qui coûtent beaucoup de temps au programmeur, tant pour le développement que pour la maintenance. Vous devez donc vous demander qu'ils prétendent avoir un mauvais comportement.

Il a été dit quelque part (citation à déterminer) que jusqu’à 70% de tout le code est constitué de l’instruction MOV de ASM, qui charge les registres de la CPU avec les variables appropriées et non le calcul en cours.

C'est ce que les processeurs font bien, ils déplacent les bits de mémoire à registres, ajouter ou les soustraient, puis les remettre en mémoire. Toute informatique se résume à peu près à ça. Remarquez, j’ai eu un jour un programme très multithread qui passait le plus clair de son temps à changer de contexte (c’est-à-dire à enregistrer et restaurer l’état des registres des threads) qu’à travailler sur le code du thread. Une simple serrure au mauvais endroit, une performance vraiment foutue, et c’était aussi un morceau de code si anodin.

Mon conseil est donc le suivant: trouvez un juste milieu raisonnable entre les deux extrêmes pour que votre code soit esthétique pour les autres utilisateurs, et testez le système pour voir s'il fonctionne correctement. Utilisez les fonctions du système d'exploitation pour vous assurer qu'il fonctionne comme vous le souhaitez avec les E / S du processeur, de la mémoire, du disque et du réseau.

gbjbaanb
la source
Je pense que cela me parle le plus en ce moment. Cela me suggère qu’un bon moyen terme est de commencer avec des concepts de mind mapping pour coder des morceaux tout en suivant et en utilisant certains concepts (comme DI), plutôt que de s’attaquer à une décomposition de code ésotérique et à un délabrement (ie tout ce que vous avez besoin ou pas).
Dennis
1
Personnellement, je trouve qu’un code plus "" moderne "" est un peu plus facile à profiler ... Je suppose que plus un code est maintenable, plus il est facile à profiler, mais il existe une limite, où casser des éléments en petits morceaux les rend moins maintenable ...
AK_
25

Sans un soin extrême, une micro-optimisation de ce type conduit à un code impossible à maintenir.

Au début, cela semble être une bonne idée, le profileur vous dit que le code est plus rapide et que V & V / Test / QA dit même que cela fonctionne. Bientôt, des bogues sont trouvés, les exigences changent et des améliorations qui n’ont jamais été prises en compte sont demandées.

Au cours de la vie d'un projet, le code se dégrade et devient moins efficace. Le code maintenable deviendra plus efficace que son équivalent non maintenable car il se dégradera plus lentement. La raison en est que le code construit l'entropie à mesure qu'il est modifié -

Le code non maintenable a rapidement plus de code mort, de chemins redondants et de duplications. Cela conduit à plus de bugs, créant un cycle de dégradation du code - y compris ses performances. D'ici peu, les développeurs sont peu convaincus que les modifications qu'ils apportent sont correctes. Cela les ralentit, les rend prudents et conduit généralement à encore plus d'entropie, car ils ne traitent que les détails qu'ils peuvent voir

Le code maintenable, avec de petits modules et des tests unitaires, est plus facile à modifier, le code devenu inutile est plus facile à identifier et à supprimer. Le code qui est cassé est également plus facile à identifier, peut être réparé ou remplacé en toute confiance.

Donc, au final, il s’agit de la gestion du cycle de vie et n’est pas aussi simple: "C’est plus rapide, ce sera toujours plus rapide".

Surtout, le code correct lent est infiniment plus rapide que le code rapide rapide.

Mattnz
la source
Merci Pour aller un peu plus loin dans mon chemin, je ne parle pas de micro-optimisation, mais d'un mouvement global vers l'écriture de code plus petit, intégrant davantage d'injections de dépendance et donc davantage de fonctionnalités extérieures et plus de "pièces mobiles" de code en général. que tous doivent être reliés entre eux pour leur travail. J'ai tendance à penser que cela produit plus de fluff de liaison / connecteur / passage variable / MOV / PUSH / POP / APPEL / JMP au niveau matériel. Je vois aussi une valeur dans le passage à la lisibilité du code, bien qu’au détriment de sacrifier les cycles de calcul au niveau matériel pour "fluff".
Dennis
10
Éviter les appels de fonction pour des raisons de performance est absolument une micro-optimisation! Sérieusement. Je ne peux pas penser à un meilleur exemple de micro-optimisation. Quelles preuves avez-vous que la différence de performance compte réellement pour le type de logiciel que vous écrivez? On dirait que vous n'en avez pas.
RibaldEddie
17

Si je comprends bien, comme vous le soulignez avec inline, sur des formes de code de niveau inférieur telles que C ++, cela peut faire une différence, mais je dis CAN à la légère.

Le site Web le résume - il n’ya pas de réponse facile . Cela dépend du système, cela dépend de ce que fait votre application, cela dépend du langage, cela dépend du compilateur et de l'optimisation.

C ++ par exemple, inline peut augmenter les performances. Plusieurs fois, cela pourrait ne rien faire, ou peut-être diminuer les performances, mais je n'ai personnellement jamais vu cela moi-même, mais j'ai entendu parler d'histoires. Inline n'est rien de plus qu'une suggestion d'optimisation au compilateur, qui peut être ignorée.

Il est probable que si vous développez des programmes de niveau supérieur, les frais généraux ne devraient pas être un problème s'il en existe un pour commencer. Les compilateurs sont extrêmement intelligents de nos jours et devraient de toute façon gérer ce genre de choses. Beaucoup de programmeurs ont un code à vivre: ne faites jamais confiance au compilateur. Si cela s'applique à vous, même de légères optimisations que vous jugez importantes peuvent l'être. Mais, gardez à l'esprit, chaque langue diffère à cet égard. Java effectue automatiquement les optimisations en ligne au moment de l'exécution. En Javascript, le contenu en ligne de votre page Web (par opposition à des fichiers séparés) est un boost et chaque milliseconde pour une page Web peut compter, mais c’est davantage un problème d’IO.

Mais sur les programmes de niveau inférieur où le programmeur utilise peut-être beaucoup de code machine et quelque chose comme le C ++, l’entendu peut faire toute la différence. Les jeux sont un bon exemple de cas dans lesquels le traitement en pipeline des processeurs est essentiel, en particulier sur les consoles, et où quelque chose comme l'inline peut s'additionner ici et là.

Une bonne lecture sur Inline spécifiquement: http://www.gotw.ca/gotw/033.htm

utilisateur99999991
la source
Du point de vue de ma question, je ne suis pas aussi concentré sur l'inline, mais sur le "câblage codifié" qui prend en compte le processus temporel de la CPU, du bus 'et des E / S liant différents morceaux de code. Je me demande s'il y a un point où il y a 50% ou plus de code de câblage et 50% du code réel que vous voulez exécuter. J'imagine qu'il y a beaucoup de duvet même dans le code le plus strict que l'on puisse écrire, et cela semble être une réalité. Une grande partie du code qui fonctionne réellement au niveau des bits et des octets est logistique: déplacer des valeurs d'un endroit à un autre, sauter à un endroit ou à un autre, et ne faisant parfois que l'ajout.
Dennis
... soustraction ou autre fonction liée aux affaires. Tout comme le déroulement de boucle peut accélérer certaines boucles en raison de la réduction du temps système alloué pour l’incrémentation de variables, l’écriture de fonctions plus volumineuses peut probablement augmenter la vitesse, à condition que votre cas d’utilisation soit configuré pour cela. Ma préoccupation ici est plus générale, voir beaucoup de conseils pour écrire des morceaux de code plus petits, augmente ce câblage, tout en améliorant la lisibilité (espérons-le), et au détriment de la saturation au niveau micro.
Dennis
3
@ Dennis - une chose à considérer est que dans un langage OO, il peut y avoir TRES peu de corrélation entre ce que le programmeur écrit (a + b) et quel code est généré (une simple addition de deux registres? Se déplace d'abord de mémoire? puis la fonction appelle l'opérateur d'un objet +?). Ainsi, les «petites fonctions» au niveau du programmeur peuvent être tout sauf petites une fois rendues en code machine.
Michael Kohne
2
@ Dennis Je peux dire que l'écriture de code ASM (directement, non compilé) pour Windows va dans le sens de "mov, mov, invoke, mov, mov, invoke". Invoke étant une macro qui effectue un appel encapsulé par des push / pops ... Parfois, vous appelez des fonctions dans votre propre code mais il est réduit à néant par tous les appels du système d'exploitation.
Brian Knoblauch
12

C'est un changement par rapport aux "anciennes" ou "mauvaises" pratiques de code dans lesquelles vous avez des méthodes couvrant 2500 lignes et de grandes classes faisant tout.

Je ne pense pas que quiconque ait jamais pensé que cela soit une bonne pratique. Et je doute que les gens qui l'ont fait l'ont fait pour des raisons de performance.

Je pense que la célèbre citation de Donald Knuth est très pertinente ici:

Nous devrions oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal.

Donc, dans 97% de votre code, utilisez simplement les bonnes pratiques, écrivez de petites méthodes (quelle que soit leur importance, je ne pense pas que toutes les méthodes devraient se composer de quelques lignes), etc. Pour les 3% restants, où les performances est important, mesurez- le. Et si les mesures montrent que le fait d’avoir beaucoup de petites méthodes ralentit considérablement votre code, vous devriez les combiner en méthodes plus grandes. Mais n'écrivez pas de code non maintenable simplement parce que cela pourrait être plus rapide.

svick
la source
11

Vous devez faire attention à écouter les programmeurs expérimentés ainsi que la pensée actuelle. Les personnes qui utilisent des logiciels de grande taille depuis des années ont quelque chose à apporter.

D'après mon expérience, voici ce qui conduit à des ralentissements, et ils ne sont pas petits. Ce sont des ordres de grandeur:

  • L'hypothèse selon laquelle une ligne de code prend à peu près autant de temps qu'une autre. Par exemple cout << endlcontre a = b + c. Le premier prend des milliers de fois plus longtemps que le dernier. Stackexchange a beaucoup de questions de la forme "J'ai essayé différentes manières d'optimiser ce code, mais cela ne semble pas faire la différence, pourquoi pas?" quand il y a un vieux grand appel de fonction au milieu.

  • L’hypothèse selon laquelle tout appel de fonction ou de méthode, une fois écrit, est bien entendu nécessaire. Les fonctions et les méthodes sont faciles à appeler et l’appel est généralement assez efficace. Le problème est qu'elles ressemblent à des cartes de crédit. Ils vous tentent de dépenser plus que vous ne le souhaitez vraiment, et ils ont tendance à cacher ce que vous avez dépensé. En plus de cela, les gros logiciels ont des couches d’abstraction, donc même s’il n’ya que 15% de déchets à chaque couche, sur 5 couches, le facteur de ralentissement est égal à 2. La solution à ce problème est de ne pas supprimer la fonctionnalité ou écrire fonctions plus importantes, c’est se discipliner pour être vigilant face à ce problème et vouloir et pouvoir le résoudre .

  • Généralité galopante. La valeur de l'abstraction est qu'elle peut vous permettre de faire plus avec moins de code - du moins, c'est l'espoir. Cette idée peut être poussée à l'extrême. Le problème avec trop de généralité est que chaque problème est spécifique, et lorsque vous le résolvez avec des abstractions générales, ces abstractions ne sont pas nécessairement capables d'exploiter les propriétés spécifiques de votre problème. Par exemple, j'ai vu une situation dans laquelle une classe de files d'attente prioritaires fantaisistes, qui pourrait être efficace pour les grandes tailles, était utilisée lorsque la longueur ne dépassait jamais 3!

  • Structure de données galopante. La programmation orientée objet est un paradigme très utile, mais elle n'incite pas à minimiser la structure des données, mais plutôt à en masquer la complexité. Par exemple, il existe le concept de "notification" dans lequel, si la donnée A est modifiée d’une manière ou d’une autre, A émet un événement de notification afin que B et C puissent également se modifier eux-mêmes afin de maintenir la cohérence de l’ensemble. Cela peut se propager sur plusieurs couches et augmenter énormément le coût de la modification. Ensuite, il est tout à fait possible que le changement en A soit rapidement annuléou changé pour encore une autre modification, ce qui signifie que l'effort déployé pour essayer de garder l'ensemble cohérent doit être fait encore une fois. Confondre c'est la probabilité de bugs dans tous ces gestionnaires de notification, la circularité, etc. Il est de loin préférable d'essayer de garder la structure de données normalisée, de sorte que tout changement ne doit être effectué qu'à un seul endroit. Si des données non normalisées ne peuvent pas être évitées, il est préférable d’avoir des passes périodiques pour remédier à l’incohérence, plutôt que de prétendre qu’elle peut être maintenue compatible avec une courte laisse.

... quand je pense à plus je vais l'ajouter.

Mike Dunlavey
la source
8

La reponse courte est oui". Et, généralement, le code sera un peu plus lent.

Mais parfois, un refactoring OO-ish approprié révélera des optimisations rendant le code plus rapide. J'ai travaillé sur un projet dans lequel nous avions créé un algorithme complexe Java, beaucoup plus complexe, avec des structures de données appropriées, des getters, etc., au lieu de tableaux désordonnés imbriqués. Mais, en isolant mieux et en limitant l'accès aux structures de données, nous avons pu passer de tableaux géants de doubles (avec des valeurs nulles pour les résultats vides) à des tableaux plus organisés de doubles, avec NaNs pour les résultats vides. Cela a permis de gagner 10 fois plus vite.

Addendum: En général, un code plus petit et mieux structuré devrait être plus adapté au multi-threading, ce qui est votre meilleur moyen d’accélérer considérablement les choses.

utilisateur949300
la source
1
Je ne vois pas pourquoi le passage de Doubles à doubles exigerait un code mieux structuré.
svick
Wow, un vote négatif? Le code client d'origine ne traitait pas avec Double.NaNs, mais vérifiait que les valeurs NULL représentaient des valeurs vides. Après restructuration, nous pourrions gérer cela (via l’encapsulation) avec les getters des différents résultats de l’algorithme. Bien sûr, nous aurions pu réécrire le code client, mais c'était plus facile.
user949300
Pour mémoire, ce n’est pas moi qui ai voté contre cette réponse.
svick
7

Entre autres choses, la programmation implique des compromis . Sur la base de ce fait, je suis enclin à répondre oui, cela pourrait être plus lent. Mais pensez à ce que vous obtenez en retour. Obtenir du code lisible, réutilisable et facilement modifiable prime facilement sur les inconvénients éventuels.

Comme @ user949300 l'a mentionné , il est plus facile de repérer les zones qui peuvent être améliorées au niveau de l'algorithme ou de l'architecture avec une telle approche; l' amélioration de ces sont généralement beaucoup plus bénéfique et efficace que de ne pas avoir possible OO ou fonction d' appel en tête ( ce qui est déjà juste un bruit, je parie).


J'imagine également que de bonnes optimisations peuvent être effectuées sur ASM avant qu'il ne soit réellement exécuté sur le matériel.

Lorsque quelque chose me plaît dans cette tête, je me souviens que les décennies passées par les personnes les plus intelligentes travaillant sur des compilateurs rendaient probablement des outils comme GCC bien meilleurs que moi pour générer du code machine. À moins que vous ne travailliez sur une sorte de choses liées aux microcontrôleurs, je vous suggère de ne pas vous en préoccuper.

Je suppose que l'ajout de plus en plus de fonctions et de plus en plus d'objets et de classes dans un code entraînera de plus en plus de passages de paramètres entre des fragments de code plus petits.

En supposant que rien lors de l'optimisation soit une perte de temps, vous avez besoin d'informations sur les performances du code. Trouver où vous programmez passe le plus clair de votre temps avec des outils spécialisés, optimisez-le, itérez.


Pour tout résumer; laissez le compilateur faire son travail et concentrez-vous sur des tâches importantes telles que l'amélioration de vos algorithmes et de vos structures de données. Tous les modèles que vous avez mentionnés dans votre question existent pour vous aider, utilisez-les.

PS: Ces 2 discours de Crockford sont apparus dans ma tête et je pense qu’ils sont un peu liés. La première est l’histoire très succincte de CS (il est toujours bon de savoir quelle que soit la science exacte); et la deuxième concerne pourquoi nous rejetons les bonnes choses.

elmigranto
la source
J'aime vraiment cette réponse le plus. Les humains ont horreur de deviner le compilateur et de prendre des photos des goulots d'étranglement. Bien sûr, vous devez être conscient de la complexité temporelle de Big-O, mais la méthode de spaghetti à 100 lignes contre une invocation de méthode d'usine + une distribution de méthode virtuelle ne devraient jamais être une discussion. la performance n’est pas du tout intéressante dans ce cas. aussi, gros avantage pour noter que "l'optimisation" sans faits réels et mesures est une perte de temps.
Sara
4

Je crois que les tendances que vous identifiez montrent bien que le développement de logiciels est une réalité: le temps de programmation est plus coûteux que le temps de calcul. Jusqu'ici, les ordinateurs sont devenus plus rapides et moins chers, mais un fouillis d'applications peut prendre des centaines, voire des milliers d'heures de travail. Compte tenu du coût des salaires, des avantages sociaux, des espaces de bureau, etc., il est plus rentable d’avoir un code qui peut s’exécuter un peu plus lentement mais qui est plus rapide et plus sûr.

Rory Hunter
la source
1
Je suis d'accord, mais les appareils mobiles sont en train de devenir si populaires que je pense qu'ils font figure d'exception. Bien que la puissance de traitement augmente, vous ne pouvez pas créer d’application pour iPhone et s’attendre à pouvoir ajouter de la mémoire comme vous le pouvez sur un serveur Web.
JeffO
3
@JeffO: Je ne suis pas d'accord - Les appareils mobiles dotés de processeurs quad core sont maintenant normaux, les performances (d'autant plus qu'elles affectent la durée de vie de la batterie), bien qu'elles soient un sujet de préoccupation, sont moins importantes que la stabilité. Un téléphone portable ou une tablette lent reçoit de mauvaises critiques, celui qui est instable est massacré. Les applications mobiles sont très dynamiques et changent presque tous les jours - la source doit suivre.
mattnz
1
@JeffO: Pour ce que cela vaut, la machine virtuelle utilisée sur Android est plutôt mauvaise. Selon les critères de référence, il pourrait être un ordre de grandeur plus lent que le code natif (alors que le meilleur de la race est généralement juste un peu plus lent). Devines quoi, cela n'intéresse personne. L’écriture de l’application est rapide et le processeur reste assis en attendant que l’utilisateur l’entre 90% du temps.
Jan Hudec
Les performances ne se limitent pas aux tests de performances bruts du processeur. Mon téléphone Android fonctionne correctement, sauf lorsque l’AV numérise une mise à jour et qu’il semble alors accrocher plus longtemps que je le souhaite, et c’est un modèle de RAM quadri-core de 2 Go! Aujourd'hui, la bande passante (réseau ou mémoire) est probablement le principal goulot d'étranglement. Votre processeur ultra-rapide tourne probablement ses pouces 99% du temps et l'expérience globale est encore médiocre.
gbjbaanb
4

Il y a bien plus de 20 ans, et j'imagine que vous n'appelez pas nouveau, ni «ancien ni mauvais», la règle était de garder les fonctions suffisamment petites pour tenir sur une page imprimée. Nous avions des imprimantes matricielles alors le nombre de lignes était quelque peu fixe, généralement un ou deux choix pour le nombre de lignes par page ... nettement inférieur à 2500 lignes.

Vous posez plusieurs questions sur le problème: maintenabilité, performance, testabilité, lisibilité. Plus vous vous penchez vers les performances, moins le code deviendra maniable et lisible. Vous devrez donc déterminer votre niveau de confort, qui peut varier et variera selon les programmeurs.

En ce qui concerne le code produit par le compilateur (code machine si vous voulez), plus la fonction est grande, plus il est possible de devoir renverser des valeurs intermédiaires dans des registres de la pile. Lorsque des cadres de pile sont utilisés, la consommation de pile est exprimée en plus gros morceaux. Plus les fonctions sont petites, plus les données restent dans des registres et moins elles dépendent de la pile. De plus petits morceaux de pile nécessaires par fonction naturellement. Les cadres empilés ont des avantages et des inconvénients en termes de performances. Des fonctions plus petites signifient plus de configuration et de nettoyage des fonctions. Bien sûr, cela dépend aussi de la manière dont vous compilez, des opportunités que vous offrez au compilateur. Vous pouvez avoir 250 fonctions de 10 lignes au lieu d’une fonction de 2 500 lignes, celle du compilateur qu’elle obtiendra si elle peut / choisit d’optimiser dans l’ensemble. Mais si vous prenez ces 250 fonctions de 10 lignes et les répartissez sur 2, 3, 4, 250 fichiers séparés, compilez chaque fichier séparément, le compilateur ne pourra pas optimiser autant de code mort qu'il pourrait en avoir. L’essentiel ici est que les deux présentent des avantages et des inconvénients et qu’il n’est pas possible d’énoncer une règle générale sur telle ou telle solution.

Des fonctions de taille raisonnable, quelque chose qu'une personne peut voir sur un écran ou une page (dans une police de caractères raisonnable), est quelque chose qu'elle peut consommer mieux et comprendre qu'un code assez volumineux. Mais s'il s'agit simplement d'une petite fonction avec des appels vers de nombreuses autres fonctions appelant de nombreuses autres fonctions, vous avez besoin de plusieurs fenêtres ou navigateurs pour comprendre ce code, de sorte que vous n'avez rien acheté du côté de la lisibilité.

la manière unix est de faire en utilisant mon terme, des blocs lego bien polis. Pourquoi voudriez-vous utiliser une fonction de bande autant d'années après que nous ayons cessé d'utiliser des bandes? Parce que le blob a très bien fait son travail et qu’au verso, nous pouvons remplacer l’interface de bande par une interface de fichier et tirer parti de la richesse du programme. Pourquoi ré-écrire le logiciel de gravure de CD-ROM simplement parce que scsi est parti car l'interface dominante a été remplacée par ide (pour revenir plus tard). Encore une fois, prenez avantage des sous-blocs polis et remplacez une extrémité par un nouveau bloc d’interface (comprenez également que les concepteurs de matériel ont simplement ajouté un bloc d’interface sur les conceptions matérielles pour que, dans certains cas, un lecteur scsi ait une interface entre interfaces scsi. , construisez des blocs lego polis de taille raisonnable, chacun ayant un objectif bien défini et des entrées et sorties bien définies. vous pouvez encapsuler les tests autour de ces blocs lego puis prendre le même bloc et encapsuler l'interface utilisateur et les interfaces du système d'exploitation autour du même bloc et du bloc, en théorie bien testé et bien compris, il ne sera pas nécessaire de déboguer, seulement les nouveaux blocs supplémentaires ajoutés à chaque extrémité. tant que toutes vos interfaces de blocs sont bien conçues et que leurs fonctionnalités sont bien comprises, vous pouvez construire beaucoup de choses avec un minimum de colle, voire aucun. comme avec des blocs lego bleus, rouges, noirs et jaunes de tailles et de formes connues, vous pouvez créer beaucoup de choses. tant que toutes vos interfaces de blocs sont bien conçues et que leurs fonctionnalités sont bien comprises, vous pouvez construire beaucoup de choses avec un minimum de colle, voire aucun. comme avec des blocs lego bleus, rouges, noirs et jaunes de tailles et de formes connues, vous pouvez créer beaucoup de choses. tant que toutes vos interfaces de blocs sont bien conçues et que leurs fonctionnalités sont bien comprises, vous pouvez construire beaucoup de choses avec un minimum de colle, voire aucun. comme avec des blocs lego bleus, rouges, noirs et jaunes de tailles et de formes connues, vous pouvez créer beaucoup de choses.

Chaque individu est différent, leur définition de poli, bien défini, testé et lisible varie. Il n’est pas déraisonnable, par exemple, pour un professeur de dicter des règles de programmation, non pas parce qu’elles peuvent être ou ne pas être mauvaises pour vous en tant que professionnel, mais dans certains cas, pour faciliter la lecture et la notation de votre code au professeur ou aux assistants d’étudiants diplômés ... Vous êtes également susceptibles, sur le plan professionnel, de constater que chaque emploi peut avoir des règles différentes pour différentes raisons, généralement une ou plusieurs personnes au pouvoir ont leur opinion sur quelque chose, juste ou faux et avec ce pouvoir elles peuvent dicter que vous en fassiez. le faire (ou quitter, se faire virer, ou arriver d’une manière ou d’une autre au pouvoir). Ces règles sont aussi souvent basées sur des opinions que sur des faits concernant la lisibilité, les performances, la testabilité et la portabilité.

old_timer
la source
«Vous devez donc trouver votre niveau de confort qui peut varier et variera d’un programmeur à l’autre». Je pense que le niveau d’optimisation devrait être déterminé par les exigences de performances de chaque élément de code et non par les préférences de chaque programmeur.
svick
Le compilateur a la certitude, en supposant que le programmeur lui a dit d'optimiser les compilateurs. Par défaut, il n'optimise pas (sur la ligne de commande, l'EDI peut avoir un défaut par défaut s'il est utilisé). Mais le programmeur n'en sait peut-être pas assez pour optimiser la taille de la fonction et avec autant de cibles que cela devient inutile. Les performances de réglage manuel ont tendance à avoir un effet négatif sur la lisibilité, et en particulier sur la maintenabilité, la testabilité peut aller dans les deux sens.
old_timer
2

Cela dépend de l'intelligence de votre compilateur. En règle générale, essayer de surpasser l'optimiseur est une mauvaise idée et peut priver le compilateur d'opportunités d'optimisation. Pour commencer, vous n'avez probablement aucune idée de ce que cela peut faire et la plupart de ce que vous faites influence réellement sa capacité à le faire aussi.

L'optimisation prématurée est la notion de programmeurs essayant de faire cela et se retrouvant avec un code difficile à maintenir qui n'était pas réellement sur le chemin critique de ce qu'ils essayaient de faire. Essayer de réduire le plus possible le volume de ressources processeur lorsque la plupart du temps, votre application est bloquée dans l'attente d'événements IO, c'est quelque chose que je vois souvent, par exemple.

Le mieux est de coder pour l'exactitude et d'utiliser un profileur pour rechercher les goulots d'étranglement réels des performances, et de les résoudre en analysant ce qui se trouve sur le chemin critique et en précisant s'il est possible de l'améliorer. Souvent, quelques solutions simples vont un long chemin.

Jilles van Gurp
la source
0

[élément heure de l'ordinateur]

Le refactoring vers un couplage plus étroit et des fonctions plus petites affecte-t-il la vitesse de code?

Oui. Mais il incombe aux interprètes, aux compilateurs et aux compilateurs JIT de supprimer ce code de "couture / câblage", et certains le font mieux que d'autres, mais d'autres non.

L'inquiétude liée aux fichiers multiples ajoute à la surcharge d'E / S, de sorte que la vitesse est également affectée (vitesse de l'ordinateur).

[élément de vitesse humaine]

(Et dois-je m'en soucier?)

non, vous ne devriez pas vous en soucier. Les ordinateurs et les circuits sont très rapides ces temps-ci et d'autres facteurs prennent le dessus, tels que la latence du réseau, les entrées / sorties de base de données et la mise en cache.

Donc, 2x à 4x ralentir dans l'exécution du code natif sera souvent noyé par ces autres facteurs.

En ce qui concerne le chargement de plusieurs fichiers, cela est souvent pris en charge par diverses solutions de mise en cache. Cela peut prendre plus de temps pour charger les éléments et les fusionner la première fois, mais à chaque fois, pour les fichiers statiques, la mise en cache fonctionne comme si un seul fichier était en cours de chargement. La mise en cache est une solution au chargement de plusieurs fichiers.

Dennis
la source
0

LA RÉPONSE (au cas où vous l'auriez manqué)

Oui, vous devriez vous en soucier, mais prenez soin de la façon dont vous écrivez le code et non de la performance.

En bref

Ne vous souciez pas de la performance

Dans le contexte de la question, des compilateurs et des interprètes plus intelligents s’occupent déjà de cette tâche.

Faites attention à écrire du code maintenable

Code où les coûts de maintenance sont au niveau de la compréhension humaine raisonnable. C'est-à-dire n'écrivez pas 1000 fonctions plus petites rendant le code incompréhensible même si vous les comprenez bien, et n'écrivez pas une fonction d'objet divin trop grande pour être comprise, mais écrivez 10 fonctions bien conçues qui ont un sens pour un humain et qui sont facile à maintenir.

Dennis
la source