La programmation modulaire affecte-t-elle le temps de calcul?

19

Tout le monde dit que je devrais rendre mon code modulaire, mais n'est-il pas moins efficace si j'utilise plus d'appels de méthode plutôt que moins, mais plus de méthodes? Quelle est la différence en Java, C ou C ++ d'ailleurs?

Je comprends qu'il est plus facile d'éditer, de lire et de comprendre, surtout en groupe. La perte de temps de calcul est-elle donc insignifiante par rapport aux avantages de l'ordre du code?

fatsokol
la source
2
La question est de savoir combien de temps il faudra pour que le temps de traitement que vous économisez passe le temps consacré à une maintenance plus difficile. La réponse à cela dépend entièrement de votre application.
Blrfl
2
De nombreuses bonnes questions génèrent un certain degré d'opinion basé sur une expérience d'expert, mais les réponses à cette question auront tendance à être presque entièrement basées sur des opinions, plutôt que sur des faits, des références ou une expertise spécifique.
gnat
10
Il convient également de souligner que la pénalité de calcul pour un appel de fonction ou de méthode est si minuscule que même dans un très grand programme avec beaucoup de fonctions et d'appels de méthode, effectuer ces appels ne se classe même pas sur le graphique.
greyfade
1
@greyfade: Cela est vrai pour les sauts directs, mais un saut indirect supplémentaire prévu pourrait coûter par exemple ~ 3% du temps total d'exécution du programme (juste un nombre du programme que j'ai récemment vérifié - il pourrait ne pas être représentatif cependant). Selon votre région, vous pourriez ou non la considérer comme significative, mais elle s'est inscrite sur la carte (et bien sûr, elle est au moins partiellement orthogonale à la modularité).
Maciej Piechotka
4
L'optimisation prématurée est la racine de tout Mal. Le code linéaire est légèrement plus rapide que le code modulaire. Le code modulaire est beaucoup plus rapide que le code spaghetti. Si vous visez du code linéaire sans un projet très (TRÈS) approfondi, vous vous retrouverez avec du code spaghetti, je vous le garantis.
SF.

Réponses:

46

Oui, ce n'est pas pertinent.

Les ordinateurs sont des moteurs d'exécution infatigables, presque parfaits, fonctionnant à des vitesses totalement comparables à celles des cerveaux. Bien qu'il existe une quantité mesurable de temps qu'un appel de fonction ajoute au temps d'exécution d'un programme, ce n'est rien comparé au temps supplémentaire nécessaire au cerveau de la prochaine personne impliquée dans le code lorsqu'elle doit démêler la routine illisible pour même commencer à comprendre comment travailler avec. Vous pouvez essayer le calcul pour une blague - supposez que votre code ne doit être conservé qu'une seule fois , et cela n'ajoute qu'une demi-heure au temps nécessaire à quelqu'un pour accepter le code. Prenez la vitesse d'horloge de votre processeur et calculez: combien de fois le code devrait-il fonctionner pour même rêver de compenser cela?

En bref, avoir pitié du processeur est complètement, totalement erroné 99,99% du temps. Pour les rares cas restants, utilisez des profileurs. Ne présumez pas que vous pouvez repérer ces cas - vous ne pouvez pas.

Kilian Foth
la source
2
Bien que je convienne que la plupart du temps c'est une optimisation prématurée, je pense que votre argument avec le temps est mauvais. Le temps est relatif dans différentes situations et vous ne pouvez pas simplement calculer comme vous l'avez fait.
Honza Brabec
28
+1 juste pour l'expression "prendre pitié du CPU", car il accentue si bien les erreurs dans l'optimisation prématurée.
Michael Borgwardt
4
Hautement lié: Quelle est la vitesse des ordinateurs au quotidien? Sortir de votre chemin pour éviter quelques appels de fonction à la «vitesse», c'est comme sortir de votre chemin pour sauver quelqu'un au total une minute au cours de sa vie . En bref: cela ne vaut même pas le temps qu'il faut pour réfléchir.
BlueRaja - Danny Pflughoeft
7
+1 pour vendre si éloquemment ce qu'il en manque, mais vous avez oublié d'ajouter le poids le plus important à l'équilibre qui rend la pitié du CPU encore plus grave: sans tenir compte de l'argent perdu et du temps consacré à cette maintenance unique; s'il a fallu 1 seconde, il y a encore un puits beaucoup plus insidieux. Risque de bug . Plus il est difficile et déroutant pour ce responsable de modifier votre code plus tard, plus il risque de l'implémenter, ce qui peut entraîner des coûts inévitablement importants du risque pour les utilisateurs et tout bug provoque une maintenance de suivi (ce qui peut entraîner une bugs ...)
Jimmy Hoffa
Un de mes anciens professeurs disait: "Si vous pensez à l'optimisation prématurée ou non, arrêtez-vous ici. Si c'était le bon choix, vous le saviez."
Ven
22

Ça dépend.

Dans le monde glacialement lent qu'est la programmation Web, où tout se passe à des vitesses humaines, une programmation lourde de méthode, où le coût de l'appel de méthode est comparable ou supérieur au coût du traitement effectué par la méthode, n'a probablement pas d'importance .

Dans le monde de la programmation de systèmes embarqués et des gestionnaires d'interruptions pour les interruptions à haut débit, cela a certainement de l'importance. Dans cet environnement, les modèles habituels «d'accès à la mémoire sont bon marché» et «le processeur est infiniment rapide» tombent en panne. J'ai vu ce qui se passe lorsqu'un programmeur orienté objet mainframe écrit son premier gestionnaire d'interruption à haut débit. Ce n'était pas joli.

Il y a plusieurs années, je faisais une coloration blob de connectivité à 8 voies non récursive sur des images FLIR en temps réel, sur ce qui était à l'époque un processeur décent. La première tentative a utilisé un appel de sous-programme, et la surcharge d'appel de sous-programme a mangé le processeur vivant. (4 appels PAR PIXEL x 64K pixels par image x 30 images par seconde = vous le comprenez). La deuxième tentative a changé le sous-programme en macro C, sans perte de lisibilité, et tout était rose.

Vous devez regarder DIFFICILEMENT ce que vous faites et l'environnement dans lequel vous le ferez.

John R. Strohm
la source
Rappelez-vous que la majorité des problèmes de programmation modernes sont mieux traités avec beaucoup de code orienté objet et orienté méthode. Vous saurez quand vous êtes dans un environnement de systèmes embarqués.
Kevin - Rétablir Monica
4
+1, mais avec une mise en garde: c'est généralement le cas qu'un compilateur d'optimisation fera souvent un meilleur travail d'inlining, de déroulement de boucle et de faire des optimisations scalaires et vectorielles qu'une personne. Le programmeur doit encore très bien connaître le langage, le compilateur et la machine pour en profiter, donc ce n'est pas magique.
detly
2
C'est vrai, mais vous avez optimisé votre code après avoir écrit la logique et l'avoir profilé pour trouver la partie problématique dans votre algorithme. Vous n'avez pas commencé à coder pour les performances mais pour la lisibilité. Et les macros vous aideront à garder votre code lisible.
Uwe Plonus du
2
Même dans la programmation de systèmes embarqués, commencez par écrire du code clair et correct et ne commencez à optimiser ensuite que si nécessaire et selon les indications du profilage . Sinon, il est trop facile d'y mettre beaucoup de travail qui n'a aucun effet réel sur les performances / la taille du code et qui rend la source difficile à comprendre.
Donal Fellows
1
@detly: Oui et non. Les compilateurs modernes peuvent en général faire un meilleur travail DANS LES LIMITES IMPOSÉES PAR LA LANGUE. Le programmeur humain sait si les limites imposées par le langage sont applicables dans le cas particulier en question. Par exemple, les compilateurs Cn et C ++ sont requis par les normes de langage pour permettre au programmeur de faire certaines choses très pathologiques et de générer du code qui fonctionne quand même. Le programmeur peut savoir qu'il n'est pas fou, ni stupide ni assez aventureux pour faire ces choses, et faire des optimisations qui seraient autrement dangereuses, car il sait qu'elles sont sûres dans ce cas.
John R. Strohm
11

Tout d'abord: les programmes dans une langue supérieure sont destinés à être lus par des humains et non par des machines.

Donc , écrire les programmes afin que vous les comprenez. Ne pensez pas aux performances (si vous avez sérieusement des problèmes de performances, profilez votre application et améliorez les performances là où elles sont nécessaires).

Même s'il est vrai que l'appel d'une méthode ou d'une fonction prend un peu de temps, cela n'a pas d'importance. Aujourd'hui, les compilateurs devraient être capables de compiler votre code dans un langage machine efficace afin que le code généré soit efficace pour l'architecture cible. Utilisez les commutateurs d'optimisation de votre compilateur pour obtenir le code efficace.

Uwe Plonus
la source
5
Je dirais que nous devrions écrire des programmes de telle manière que les autres les comprennent.
Bartlomiej Lewandowski
La première personne qui doit lire un programme est le développeur lui-même. Voilà pourquoi je vous ai écrit . Si d'autres personnes peuvent lire le programme, c'est agréable mais (à mes yeux) pas nécessaire (en premier lieu). Si vous travaillez en équipe, d'autres personnes devraient également comprendre le programme, mais mon intention était que cette personne doive lire un programme, pas des ordinateurs.
Uwe Plonus
5

Généralement, lorsque vous auriez autrement une grande fonction et que vous la diviseriez en beaucoup de plus petites, ces plus petites seront alignées parce que le seul inconvénient de l'inline (répétant trop les mêmes instructions) n'est pas pertinent dans ce cas. Cela signifie que votre code agira comme si vous aviez écrit une grande fonction.

S'ils ne sont pas alignés pour une raison quelconque et que cela devient un problème de performances, vous devez alors envisager un alignement manuel. Toutes les applications ne sont pas des formulaires CRUD en réseau avec d'énormes latences intrinsèques.

Esailija
la source
2

Il n'y a probablement aucun coût de calcul. Habituellement, les compilateurs / JIT depuis 10 à 20 ans environ traitent parfaitement la fonction d'inline. Pour le C / C ++, il est généralement limité aux fonctions «inlinables» (c'est-à-dire que la définition de la fonction est disponible pour le compilateur pendant la compilation - c'est-à-dire qu'elle se trouve dans l'en-tête du même fichier), mais les techniques actuelles de LTO le surmontent.

Si vous devez consacrer du temps à l'optimisation, cela dépend de la zone sur laquelle vous travaillez. Si vous traitez avec une application «normale» qui a passé la plupart du temps à attendre la saisie - vous ne devriez probablement pas vous soucier des optimisations à moins que l'application «ne semble» lente.

Même dans de tels cas, vous devez vous concentrer sur beaucoup de choses avant de faire de la micro-optimisation:

  • Où sont les problèmes? Habituellement, les gens ont du mal à trouver les hotspots car nous lisons le code source différemment. Nous avons différents ratios de temps de fonctionnement et nous les exécutons séquentiellement, contrairement aux processeurs modernes .
  • Faut-il faire un calcul à chaque fois? Par exemple, si vous modifiez un seul paramètre sur des milliers, vous souhaiterez peut-être calculer uniquement une partie affectée au lieu du modèle entier.
  • Utilisez-vous un algorithme optimal? Le passage de O(n)à O(log n)pourrait avoir un impact beaucoup plus important que tout ce que vous pourriez réaliser par micro-optimisation.
  • Utilisez-vous des structures appropriées? Supposons que vous utilisez un Listquand vous en avez besoin HashSetpour avoir des O(n)recherches quand vous pourriez en avoir O(1).
  • Utilisez-vous efficacement le parallélisme? Actuellement, même les téléphones mobiles peuvent avoir 4 cœurs ou plus, il peut être tentant d'utiliser des threads. Cependant, ils ne sont pas une solution miracle car ils ont un coût de synchronisation (sans mentionner que si le problème est lié à la mémoire, ils n'ont aucun sens de toute façon).

Même si vous décidez que vous devez effectuer une micro-optimisation (ce qui signifie pratiquement que votre logiciel est utilisé dans HPC, intégré ou simplement utilisé par un très grand nombre de personnes - sinon le coût supplémentaire de maintenance dépasse les coûts de temps de l'ordinateur) dont vous avez besoin pour identifier les hotspots (noyaux) que vous souhaitez accélérer. Mais alors vous devriez probablement:

  1. Connaître exactement la plateforme sur laquelle vous travaillez
  2. Connaître exactement le compilateur sur lequel vous travaillez et quelle optimisation il est capable de faire et comment écrire du code idiomatique pour permettre ces optimisations
  3. Pensez aux modèles d'accès à la mémoire et à la quantité que vous pouvez insérer dans le cache (dont vous connaissez la taille exacte au point 1).
  4. Ensuite, si vous êtes lié au calcul, pensez à réorganiser le calcul pour enregistrer le calcul.

Une dernière remarque. Habituellement, le seul problème que vous rencontrez avec les appels de méthode est les sauts indirects (méthodes virtuelles) qui n'étaient pas prédits par le prédicteur de branche (malheureusement, le saut indirect en est le cas difficile). Pourtant:

  • Java a un JIT qui dans de nombreux cas peut prédire le type de classe et donc une cible de saut à l'avance et donc dans les hotspots vous ne devriez pas avoir beaucoup de problèmes.
  • Les compilateurs C ++ effectuent souvent une analyse de programme et au moins dans certains cas peuvent prédire la cible au moment de la compilation.
  • Dans les deux cas, lorsque la cible a été prédite, l'inline devrait fonctionner. Si le compilateur ne pouvait pas exécuter les chances d'inline, nous ne le pourrions pas non plus.
Maciej Piechotka
la source
0

Ma réponse ne développera probablement pas trop les réponses existantes, mais je pense que mes deux cents pourraient être utiles.

Tout d'abord; oui, pour la modularité, vous abandonnez généralement un certain niveau de temps d'exécution. Tout écrire dans le code assembleur vous donnera la meilleure vitesse. Cela dit...

Vous connaissez YouTube? Probablement soit le site à la bande passante la plus élevée qui existe, soit le deuxième derrière Netflix? Ils écrivent une grande partie de leur code en Python, qui est un langage hautement modulaire pas tout à fait conçu pour des performances de premier ordre.

Le problème est que, lorsque quelque chose ne va pas et que les utilisateurs se plaignent du chargement lent des vidéos, il n'y a pas beaucoup de scénarios où cette lenteur serait finalement attribuée à la vitesse d'exécution lente de Python. Cependant, la recompilation rapide de Python et sa capacité modulaire à essayer de nouvelles choses sans vérification de type permettront probablement aux ingénieurs de déboguer ce qui ne va pas assez rapidement ("Wow. Notre nouveau stagiaire a écrit une boucle qui fait une nouvelle sous-requête SQL pour CHAQUE résultat. ") ou (" Oh, Firefox a déprécié l'ancien format d'en-tête de mise en cache; et ils ont créé une bibliothèque Python pour configurer le nouveau facilement ")

En ce sens, même en termes de temps d'exécution, un langage modulaire peut être considéré comme plus rapide car une fois que vous aurez identifié vos goulots d'étranglement, il deviendra probablement plus facile de réorganiser votre code pour le faire fonctionner de la meilleure façon. Tant d'ingénieurs vous diront que les hits de performances élevées n'étaient pas là où ils pensaient qu'ils seraient (et en fait, les choses qu'ils ont DID optimisées n'étaient guère nécessaires; ou, ne fonctionnaient même pas comme ils l'attendaient!)

Katana314
la source
0

Oui et non. Comme d'autres l'ont noté le programme pour la lisibilité d'abord, puis pour l'efficacité. Cependant, il existe des pratiques standard qui sont à la fois lisibles et efficaces. La plupart du code est exécuté assez rarement, et vous ne tirerez aucun avantage de l'optimisation de toute façon.

Java peut incorporer des appels de fonction plus petits, il n'y a donc pas de raison d'éviter d'écrire les fonctions. Les optimiseurs ont tendance à mieux fonctionner avec un code plus simple à lire. Il existe des études qui montrent que les raccourcis qui devraient théoriquement s'exécuter plus rapidement, prennent en fait plus de temps. Le compilateur JIT est susceptible de mieux fonctionner, le code est plus petit et les morceaux exécutés fréquemment peuvent être identifiés et optimisés. Je ne l'ai pas essayé mais je m'attendrais à une grande fonction qui est relativement rarement appelée pour ne pas être compilée.

Cela ne s'applique probablement pas à Java, mais une étude a révélé que les fonctions plus importantes fonctionnaient plus lentement en raison de la nécessité d'un modèle de référence de mémoire différent. C'était spécifique au matériel et à l'optimiseur. Pour les modules plus petits, des instructions fonctionnant dans une page mémoire ont été utilisées. Celles-ci étaient plus rapides et plus petites que les instructions requises lorsque la fonction ne cadrait pas avec une page.

Il y a des cas où il vaut la peine d'optimiser le code, mais généralement vous devez profiler le code pour déterminer où il se trouve. Je trouve que ce n'est souvent pas le code que j'attendais.

BillThor
la source