Je voudrais demander aux personnes expérimentées dans l'utilisation de systèmes à l'échelle de Visual Studio: qu'est-ce qui les ralentit? S'agit-il des couches sur les couches d'abstractions nécessaires pour maintenir la base de code dans les capacités de compréhension humaine? Est-ce la quantité de code à parcourir? Est-ce la tendance moderne vers des approches permettant de gagner du temps au programmeur aux dépens (énormément énormes) des cycles d'horloge / du service d'utilisation de la mémoire?
Je pense que vous en avez deviné un certain nombre, mais j'aimerais offrir ce que je considère comme le plus gros facteur, ayant travaillé sur une base de code assez grande (je ne sais pas si elle est aussi grande que Visual Studio - était dans les millions de lignes de code catégorie et environ un millier de plugins) pendant environ 10 ans et des phénomènes d'observation se produisent.
C'est aussi un peu moins controversé car il n'entre pas dans les API ou les fonctionnalités de langage ou quelque chose comme ça. Celles-ci concernent les «coûts» qui peuvent engendrer un débat plutôt que les «dépenses», et je veux me concentrer sur les «dépenses».
Coordination lâche et héritage
Ce que j'ai observé, c'est qu'une mauvaise coordination et un long héritage ont tendance à entraîner beaucoup de déchets accumulés.
Par exemple, j'ai trouvé une centaine de structures d'accélération dans cette base de code, dont beaucoup sont redondantes.
Nous aimerions avoir un arbre KD pour accélérer un moteur physique, un autre pour un nouveau moteur physique qui fonctionnait souvent en parallèle avec l'ancien, nous aurions des dizaines d'implémentations d'octrees pour divers algorithmes de maillage, un autre arbre KD pour le rendu , cueillette, etc., etc., etc. Ce sont toutes de grandes structures d'arbres volumineuses utilisées pour accélérer les recherches. Chaque individu peut prendre des centaines de mégaoctets en gigaoctets de mémoire pour une entrée de taille très moyenne. Ils n'étaient pas toujours instanciés et utilisés tout le temps, mais à tout moment, 4 ou 5 d'entre eux pouvaient être en mémoire simultanément.
Maintenant, tous ces éléments stockaient exactement les mêmes données pour accélérer leur recherche. Vous pouvez l'imaginer comme une ancienne base de données analogique qui stocke tous ses champs dans 20 cartes / dictionnaires / arbres B + redondants différents à la fois, organisés de manière identique par les mêmes clés, et les recherche tout le temps. Maintenant, nous prenons 20 fois plus de mémoire et de traitement.
De plus, en raison de la redondance, il y a peu de temps pour optimiser l'un d'eux avec le prix de maintenance qui l'accompagne, et même si nous le faisions, cela n'aurait que 5% de l'effet idéalement.
Qu'est-ce qui cause ce phénomène? Une mauvaise coordination était la cause numéro un que j'ai vue. Beaucoup de membres de l'équipe travaillent souvent dans leurs écosystèmes isolés, développant ou utilisant des structures de données tierces, mais n'utilisant pas les mêmes structures que les autres membres de l'équipe utilisaient même s'ils étaient des doublons flagrants des mêmes préoccupations.
Qu'est-ce qui fait que ce phénomène persiste? L'héritage et la compatibilité étaient la cause numéro un que j'ai vue. Comme nous avons déjà payé le coût de la mise en œuvre de ces structures de données et que de grandes quantités de code dépendaient de ces solutions, il était souvent trop risqué d'essayer de les consolider en moins de structures de données. Même si bon nombre de ces structures de données étaient très redondantes sur le plan conceptuel, elles n'étaient pas toujours proches de l'identique dans leur conception d'interface. Les remplacer aurait donc été un grand changement risqué au lieu de les laisser consommer de la mémoire et du temps de traitement.
Efficacité de la mémoire
En règle générale, l'utilisation de la mémoire et la vitesse ont tendance à être liées au moins au niveau global. Vous pouvez souvent repérer les logiciels lents par la façon dont ils accaparent la mémoire. Ce n'est pas toujours vrai que plus de mémoire conduit à un ralentissement, car ce qui compte, c'est la mémoire «chaude» (quelle mémoire est consultée en permanence - si un programme utilise une charge de mémoire mais seulement 1 mégaoctet est utilisé tout le temps, alors ce n’est pas un problème de vitesse).
Vous pouvez donc repérer les porcs potentiels en fonction de l'utilisation de la mémoire la plupart du temps. Si une application prend des dizaines à des centaines de mégaoctets de mémoire au démarrage, elle ne sera probablement pas très efficace. Des dizaines de mégaoctets peuvent sembler petits lorsque nous avons des gigaoctets de DRAM de nos jours, mais les caches de processeur les plus grands et les plus lents sont toujours dans la plage de mégaoctets minables, et les plus rapides sont toujours dans la plage de kilo-octets. Par conséquent, un programme qui utilise 20 mégaoctets juste pour démarrer et ne rien faire utilise en fait assez "beaucoup" de mémoire du point de vue du cache du processeur matériel, surtout si les 20 mégaoctets de cette mémoire seront accessibles à plusieurs reprises et fréquemment pendant l'exécution du programme.
Solution
Pour moi, la solution consiste à rechercher des équipes plus coordonnées et plus petites pour créer des produits, celles qui peuvent en quelque sorte suivre leurs "dépenses" et éviter "d'acheter" les mêmes articles encore et encore et encore.
Coût
Je vais plonger dans le côté «coût» plus controversé juste un tout petit peu avec un phénomène de «dépenses» que j'ai observé. Si un langage finit par avoir un prix inévitable pour un objet (comme celui qui fournit une réflexion à l'exécution et ne peut pas forcer l'allocation contiguë pour une série d'objets), ce prix n'est cher que dans le contexte d'un élément très granulaire, comme un unique Pixel
ou Boolean
.
Pourtant, je vois beaucoup de code source pour les programmes qui gèrent une lourde charge (par exemple: gérer des centaines de milliers à des millions de cas Pixel
ou des Boolean
instances) payant ce coût à un niveau aussi granulaire.
La programmation orientée objet peut en quelque sorte exacerber cela. Pourtant, ce n'est pas le coût des "objets" en soi ou même la POO en faute, c'est simplement que ces coûts sont payés à un niveau si granulaire d'un élément minuscule qui va être instancié par des millions.
Voilà donc les autres phénomènes de «coûts» et de «dépenses» que j'observe. Le coût est de quelques sous, mais les sous s'additionnent si nous achetons un million de canettes de soda individuellement au lieu de négocier avec un fabricant pour un achat en gros.
La solution ici pour moi est l'achat "en gros". Les objets sont parfaitement bien, même dans les langues qui ont une étiquette de prix pour chacun à condition que ce coût ne soit pas payé individuellement un million de fois pour l'équivalent analogique d'une canette de soda.
Optimisation prématurée
Je n'ai jamais vraiment aimé le libellé utilisé par Knuth ici, car "l'optimisation prématurée" accélère rarement les programmes de production dans le monde réel. Certains interprètent cela comme «l'optimisation précoce», alors que Knuth signifiait davantage «l'optimisation sans les connaissances / l'expérience appropriées pour connaître son véritable impact sur le logiciel». Si quoi que ce soit, l'effet pratique d' une véritable optimisation prématurée va souvent au logiciel de faire plus lent , car la dégradation des moyens de maintenabilité , il y a peu de temps pour optimiser les chemins critiques qui comptent vraiment .
C'est le dernier phénomène que j'ai observé, où les développeurs tentaient d'économiser des sous sur l'achat d'une seule canette de soda, pour ne plus jamais acheter, ou pire, une maison, perdaient tout leur temps à pincer des sous (ou pire, des sous imaginaires de ne pas comprendre leur compilateur ou l'architecture du matériel) alors qu'il y avait des milliards de dollars dépensés inutilement ailleurs.
Le temps est très limité, donc essayer d'optimiser les absolus sans avoir les informations contextuelles appropriées nous prive souvent de la possibilité d'optimiser les endroits qui comptent vraiment, et donc, en termes d'effet pratique, je dirais que "l'optimisation prématurée rend le logiciel beaucoup plus lent. "
Le problème est qu'il existe des types de développeurs qui prendront ce que j'ai écrit ci-dessus à propos des objets et essaieront d'établir une norme de codage qui interdit la programmation orientée objet ou quelque chose de fou de ce genre. Une optimisation efficace est une priorisation efficace, et cela ne vaut absolument rien si nous nous noyons dans une mer de problèmes de maintenance.