Question d'ingénieur débutant concernant la gestion de la mémoire


9

Cela fait quelques mois que j'ai commencé mon poste de développeur de logiciels d'entrée de gamme. Maintenant que j'ai dépassé certaines courbes d'apprentissage (par exemple le langage, le jargon, la syntaxe de VB et C #), je commence à me concentrer sur des sujets plus ésotériques, comme pour écrire de meilleurs logiciels.

Une question simple que j'ai posée à un collègue a été répondue par "Je me concentre sur les mauvaises choses". Bien que je respecte ce collègue, je ne suis pas d'accord que ce soit une «mauvaise chose» sur laquelle se concentrer.

Voici le code (en VB) et suivi de la question.

Remarque: La fonction GenerateAlert () renvoie un entier.

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

contre...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

J'ai écrit à l'origine ce dernier et l'ai réécrit avec le "Dim alertID" afin que quelqu'un d'autre puisse le lire plus facilement. Mais voici ma préoccupation et ma question:

Si l'on écrivait cela avec le Dim AlertID, cela prendrait en fait plus de mémoire; fini mais plus, et cette méthode devrait-elle être appelée plusieurs fois pourrait-elle conduire à un problème? Comment .NET va gérer cet objet AlertID. En dehors de .NET, il faut éliminer manuellement l'objet après utilisation (vers la fin du sous-marin).

Je veux m'assurer de devenir un programmeur compétent qui ne se fonde pas uniquement sur la collecte des ordures. Suis-je trop penser à cela? Suis-je en train de me concentrer sur les mauvaises choses?


1
Je pourrais facilement faire valoir qu'il est à 100% puisque la première version est plus lisible. Je parie que le compilateur peut même s'occuper de ce qui vous préoccupe. Même si ce n'est pas le cas, vous optimisez prématurément.
Rig

6
Je ne suis pas du tout sûr qu'il utiliserait vraiment plus de mémoire avec un entier anonyme par rapport à un entier nommé. En tout cas, c'est vraiment une optimisation prématurée. Si vous devez vous soucier de l'efficacité à ce niveau (je suis presque sûr que vous ne le faites pas), vous pourriez avoir besoin de C ++ au lieu de C #. Il est bon de comprendre les problèmes de performances et ce qui se passe sous le capot, mais celui-ci est un petit arbre dans une grande forêt.
psr

5
L'entier nommé vs anonyme n'utiliserait pas plus de mémoire, d'autant plus qu'un entier anonyme est juste un entier nommé que VOUS n'avez pas nommé (le compilateur doit toujours le nommer). Tout au plus, l'entier nommé aurait une portée différente de sorte qu'il pourrait vivre plus longtemps. L'entier anonyme ne vivrait que tant que la méthode en aurait besoin, celui nommé vivrait aussi longtemps que son parent en aurait besoin.
Joel Etherton

Voyons voir ... Si Integer est une classe, elle sera allouée sur le tas. La variable locale (sur la pile la plus probable) contiendra une référence à elle. La référence sera transmise à l'objet errorDictionary. Si le runtime effectue le comptage de références (ou autre), alors lorsqu'il n'y a plus de références, il (l'objet) sera désalloué du tas. Tout ce qui se trouve sur la pile est automatiquement «désalloué» une fois la méthode terminée. S'il s'agit d'une primitive, elle finira (très probablement) sur la pile.
Paul

Votre collègue avait raison: le problème soulevé par votre question n'aurait pas dû concerner l'optimisation, mais la lisibilité .
haylem

Réponses:


25

"L'optimisation prématurée est la racine de tout mal (ou du moins la plupart) dans la programmation." - Donald Knuth

Quand il s'agit de votre premier passage, écrivez simplement votre code afin qu'il soit correct et propre. S'il est ultérieurement déterminé que votre code est critique en termes de performances (il existe des outils pour déterminer ce que l'on appelle des profileurs), il peut être réécrit. Si votre code n'est pas jugé critique pour les performances, la lisibilité est beaucoup plus importante.

Vaut-il la peine de creuser dans ces sujets de performance et d'optimisation? Absolument, mais pas sur le dollar de votre entreprise si cela n'est pas nécessaire.


1
Sur qui d'autre le dollar devrait-il être? Votre employeur profite de l'augmentation de vos compétences plus que vous.
Marcin

Des sujets qui ne contribuent pas directement à votre tâche actuelle? Vous devriez poursuivre ces choses à votre rythme. Si je m'asseyais et recherchais chaque élément CompSci qui a piqué ma curiosité au cours de la journée, je ne ferais rien. C'est à ça que servent mes soirées.
Christopher Berman

2
Bizarre. Certains d'entre nous ont une vie personnelle et, comme je l'ai dit, l'employeur profite principalement de la recherche. La clé est de ne pas y passer toute la journée.
Marcin

6
Bien pour vous. Mais cela n'en fait pas vraiment une règle universelle. De plus, si vous découragez vos employés d'apprendre au travail, tout ce que vous avez fait est de les décourager d'apprendre et de les encourager à trouver un autre employeur qui paie réellement pour le développement du personnel.
Marcin

2
Je comprends les opinions notées dans les commentaires ci-dessus; J'aimerais noter que j'ai demandé pendant ma pause déjeuner. :). Encore une fois, merci à tous pour votre contribution ici et sur le site Stack Exchange; c'est inestimable!
Sean Hobbs

5

Pour votre programme .NET moyen, oui, c'est une réflexion excessive. Il peut y avoir des moments où vous voudrez en savoir plus sur ce qui se passe exactement à l'intérieur de .NET, mais cela est relativement rare.

L'une des transitions difficiles que j'ai eues a été de passer de l'utilisation de C et MASM à la programmation en VB classique dans les années 90. J'avais l'habitude d'optimiser tout pour la taille et la vitesse. J'ai dû abandonner cette pensée pour la plupart et laisser VB faire sa chose pour être efficace.


5

Comme mon collègue disait toujours:

  1. Fais-le fonctionner
  2. Corrigez tous les bugs pour qu'il fonctionne parfaitement
  3. Rendez-le SOLIDE
  4. Appliquer l'optimisation si elle fonctionne lentement

En d'autres termes, gardez toujours à l'esprit KISS (gardez-le simple stupide). Parce qu'une ingénierie excessive, une réflexion excessive sur la logique du code peut être un problème pour changer la logique la prochaine fois. Cependant, garder le code propre et simple est toujours une bonne pratique .

Cependant, avec le temps et l'expérience, vous sauriez mieux quel code sent et aurait besoin d'une optimisation très bientôt.


3

Faut-il écrire ceci avec le Dim AlertID

La lisibilité est importante. Dans votre exemple , si, je ne suis pas sûr que vous vraiment faire des choses que plus lisible. GenerateAlert () a un bon nom et n'ajoute pas beaucoup de bruit. Il y a probablement de meilleures utilisations de votre temps.

cela prendrait en fait plus de mémoire;

Je suppose que non. C'est une optimisation relativement simple à réaliser pour le compilateur.

cette méthode doit-elle être appelée plusieurs fois peut-elle entraîner un problème?

L'utilisation d'une variable locale comme intermédiaire n'a aucun impact sur le garbage collector. Si la mémoire haute de GenerateAlert () new, alors cela importera. Mais cela importera indépendamment de la variable locale ou non.

Comment .NET va gérer cet objet AlertID.

AlertID n'est pas un objet. Le résultat de GenerateAlert () est l'objet. AlertID est la variable qui, s'il s'agit d'une variable locale, est simplement un espace associé à la méthode pour garder une trace des choses.

En dehors de .NET, il faut éliminer manuellement l'objet après utilisation

Il s'agit d'une question plus délicate qui dépend du contexte impliqué et de la sémantique de propriété de l'instance fournie par GenerateAlert (). En général, tout ce qui a créé l'instance doit la supprimer. Votre programme serait probablement très différent s'il était conçu avec une gestion manuelle de la mémoire à l'esprit.

Je veux m'assurer de devenir un programmeur compétent qui ne se contente pas de relayer la collecte des ordures. Suis-je trop penser à cela? Suis-je en train de me concentrer sur les mauvaises choses?

Un bon programmeur utilise les outils à sa disposition, y compris le garbage collector. Il vaut mieux penser à autre chose que vivre inconscient. Vous vous concentrez peut-être sur les mauvaises choses, mais puisque nous sommes ici, vous pourriez tout aussi bien en savoir plus.


2

Faites-le fonctionner, rendez-le propre, rendez-le SOLIDE, PUIS faites-le fonctionner aussi vite qu'il le faut .

Cela devrait être l'ordre normal des choses. Votre toute première priorité est de faire quelque chose qui passera les tests d'acceptation qui secouent les exigences. C'est votre première priorité car c'est la première priorité de votre client; répondre aux exigences fonctionnelles dans les délais de développement. La prochaine priorité est d'écrire du code propre et lisible, facile à comprendre et qui peut ainsi être maintenu par votre postérité sans WTF lorsque cela devient nécessaire (il n'est presque jamais question de "si"; vous ou quelqu'un après vous devrez aller revenir et changer / réparer quelque chose). La troisième priorité est de faire adhérer le code à la méthodologie SOLID (ou GRASP si vous préférez), qui met le code en morceaux modulaires, réutilisables et remplaçables qui facilitent à nouveau la maintenance (non seulement ils peuvent comprendre ce que vous avez fait et pourquoi, mais il y a des lignes épurées le long desquelles je peux supprimer et remplacer chirurgicalement des morceaux de code). La dernière priorité est la performance; si le code est suffisamment important pour être conforme aux spécifications de performance, il est presque certainement assez important pour être rendu correct, propre et SOLIDE en premier.

Faisant écho à Christopher (et Donald Knuth), "l'optimisation prématurée est la racine de tout mal". De plus, le type d'optimisations que vous envisagez est à la fois mineur (une référence à votre nouvel objet sera créée sur la pile, que vous lui donniez un nom dans le code source ou non) et d'un type qui ne peut causer aucune différence dans la compilation IL. Les noms de variable ne sont pas reportés dans l'IL, donc puisque vous déclarez la variable juste avant sa première (et probablement seule) utilisation, je parierais de l'argent de la bière que l'IL est identique entre vos deux exemples. Ainsi, votre collègue a 100% raison; vous cherchez au mauvais endroit si vous cherchez une variable nommée vs une instanciation en ligne pour quelque chose à optimiser.

Les micro-optimisations en .NET n'en valent presque jamais la peine (je parle de 99,99% des cas). En C / C ++, peut-être, SI vous savez ce que vous faites. Lorsque vous travaillez dans un environnement .NET, vous êtes déjà suffisamment loin du métal du matériel pour qu'il y ait une surcharge importante dans l'exécution de code. Donc, étant donné que vous êtes déjà dans un environnement qui indique que vous avez renoncé à la vitesse fulgurante et que vous cherchez plutôt à écrire du code "correct", si quelque chose dans un environnement .NET ne fonctionne vraiment pas assez rapidement, sa complexité est trop élevé, ou vous devriez envisager de le paralléliser. Voici quelques conseils de base à suivre pour l'optimisation; Je vous garantis que votre productivité en optimisation (vitesse gagnée pour le temps passé) montera en flèche:

  • La modification de la forme de la fonction importe plus que la modification des coefficients - complexité WRT Big-Oh, vous pouvez réduire de moitié le nombre d'étapes qui doivent être exécutées dans un algorithme N 2 , et vous disposez toujours d'un algorithme à complexité quadratique même s'il s'exécute en la moitié du temps. Si c'est la limite inférieure de complexité pour ce type de problème, qu'il en soit ainsi, mais s'il existe une solution NlogN, linéaire ou logarithmique au même problème, vous gagnerez plus en changeant d'algorithmes pour réduire la complexité qu'en optimisant celui que vous avez.
  • Tout simplement parce que vous ne pouvez pas voir la complexité ne signifie pas qu'il ne vous coûte - Plusieurs des plus élégants d' une seule ligne dans le mot effectuer terriblement (par exemple, le premier vérificateur Regex est une fonction de la complexité exponentielle, alors que l' efficacité l'évaluation principale consistant à diviser le nombre par tous les nombres premiers inférieurs à sa racine carrée est de l'ordre de O (Nlog (sqrt (N))). Linq est une excellente bibliothèque car elle simplifie le code, mais contrairement à un moteur SQL, le .Net Le compilateur n'essayera pas de trouver le moyen le plus efficace d'exécuter votre requête. Vous devez savoir ce qui se passera lorsque vous utiliserez une méthode, et donc pourquoi une méthode pourrait être plus rapide si elle était placée plus tôt (ou plus tard) dans la chaîne, tout en produisant les mêmes résultats.
  • OTOH, il y a presque toujours un compromis entre la complexité de la source et la complexité de l'exécution - SelectionSort est très facile à implémenter; vous pourriez probablement le faire en 10LOC ou moins. MergeSort est un peu plus complexe, Quicksort plus et RadixSort encore plus. Mais, à mesure que les algorithmes augmentent en complexité de codage (et donc en temps de développement "initial"), ils diminuent en complexité d'exécution; MergeSort et QuickSort sont NlogN, et RadixSort est généralement considéré comme linéaire (techniquement c'est NlogM où M est le plus grand nombre en N).
  • Break fast - S'il y a un contrôle qui peut être fait à peu de frais et qui est vraisemblablement vrai et qui signifie que vous pouvez continuer, faites ce contrôle en premier. Si votre algorithme, par exemple, ne se soucie que des nombres qui se terminent par 1, 2 ou 3, le cas le plus probable (étant donné des données complètement aléatoires) est un nombre qui se termine par un autre chiffre, alors testez que le nombre ne se termine pas par 1, 2 ou 3, avant de faire des vérifications pour voir si le nombre se termine par 1, 2 ou 3. Si une logique nécessite A&B, et P (A) = 0,9 tandis que P (B) = 0,1, alors vérifiez B en premier, sauf si! A puis! B (comme if(myObject != null && myObject.someProperty == 1)), ou B prend plus de 9 fois plus de temps que A pour évaluer ( if(myObject != null && some10SecondMethodReturningBool())).
  • Ne posez aucune question à laquelle vous connaissez déjà la réponse. - Si vous avez une série de conditions de "passage" et qu'une ou plusieurs de ces conditions dépendent d'une condition plus simple qui doit également être vérifiée, ne cochez jamais les deux ces indépendamment. Par exemple, si vous avez un chèque qui nécessite A et un chèque qui nécessite A && B, vous devez cocher A, et si c'est vrai, vous devez cocher B. Si! A, alors! A && B, alors ne vous embêtez même pas.
  • Plus vous faites quelque chose, plus vous devez prêter attention à la façon dont cela est fait - C'est un thème commun dans le développement, à plusieurs niveaux; dans un sens de développement général, "si une tâche commune prend du temps ou est fastidieuse, continuez à le faire jusqu'à ce que vous soyez à la fois frustré et suffisamment informé pour trouver un meilleur moyen". En termes de code, plus un algorithme inefficace est exécuté, plus vous gagnerez en performances globales en l'optimisant. Il existe des outils de profilage qui peuvent prendre un assembly binaire et ses symboles de débogage et vous montrer, après avoir parcouru certains cas d'utilisation, quelles lignes de code ont été exécutées le plus. Ces lignes, et les lignes qui les exécutent, sont celles auxquelles vous devriez faire le plus attention, car toute augmentation de l'efficacité que vous y obtiendrez sera multipliée.
  • Un algorithme plus complexe ressemble à un algorithme moins complexe si vous lui lancez suffisamment de matériel . Il y a des moments où vous devez simplement vous rendre compte que votre algorithme approche des limites techniques du système (ou de la partie de celui-ci) sur lequel vous l'exécutez; à partir de ce moment, s'il doit être plus rapide, vous gagnerez plus en l'exécutant simplement sur un meilleur matériel. Cela s'applique également à la parallélisation; un algorithme de complexité N 2 , lorsqu'il est exécuté sur N cœurs, semble linéaire. Donc, si vous êtes sûr d'avoir atteint la limite inférieure de complexité pour le type d'algorithme que vous écrivez, cherchez des moyens de "diviser pour mieux régner".
  • C'est rapide quand c'est assez rapide - À moins que vous n'assembliez l'assemblage à la main pour cibler une puce particulière, il y a toujours quelque chose à gagner. Cependant, à moins que vous VOULEZ être assemblé à la main, vous devez toujours garder à l'esprit ce que le client qualifierait de «suffisamment bon». Encore une fois, "l'optimisation prématurée est la racine de tout mal"; lorsque votre client l'appelle assez rapidement, vous avez terminé jusqu'à ce qu'il ne pense plus que c'est assez rapide.

0

Le seul moment où vous devez vous soucier de l'optimisation dès le début, c'est lorsque vous savez que vous avez affaire à quelque chose d'énorme ou que vous savez qu'il s'exécutera un grand nombre de fois.

La définition de «énorme» varie évidemment en fonction de ce que sont vos systèmes cibles.


0

Je préférerais la version à deux lignes simplement parce qu'il est plus facile de passer avec un débogueur. Une ligne avec plusieurs appels intégrés rend la tâche plus difficile.

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.