Comment éviter la malédiction de l'optimisation prématurée

Publié: 2022-03-11

C'est presque digne de garantie, vraiment. Des novices aux experts, de l'architecture à l'ASM, et de l'optimisation de tout, des performances de la machine aux performances des développeurs, il y a de fortes chances que vous et votre équipe court-circuitiez vos propres objectifs.

Quoi? Moi? Mon équipe ?

C'est une accusation assez lourde à porter. Laisse-moi expliquer.

L'optimisation n'est pas le Saint Graal, mais elle peut être tout aussi difficile à obtenir. Je souhaite partager avec vous quelques conseils simples (et une montagne d'embûches) pour vous aider à transformer l'expérience de votre équipe d'une expérience d'auto-sabotage en une expérience d'harmonie, d'épanouissement, d'équilibre et, éventuellement, d'optimisation.

Qu'est-ce que l'optimisation prématurée ?

L'optimisation prématurée tente d'optimiser les performances :

  1. Lors du premier codage d'un algorithme
  2. Avant que les benchmarks ne confirment que vous devez
  3. Avant de profiler, identifiez où il est logique de s'embêter à optimiser
  4. À un niveau inférieur à celui que votre projet dicte actuellement

Maintenant, je suis un optimiste, Optimus.

Au moins, je vais faire semblant d'être optimiste pendant que j'écris cet article. De votre côté, vous pouvez prétendre que votre nom est Optimus, cela vous parlera donc plus directement.

En tant que personne dans le domaine de la technologie, vous vous demandez probablement parfois comment cela pourrait être $year et pourtant, malgré tous nos progrès, c'est en quelque sorte une norme acceptable pour que $task soit si chronophage. Vous voulez être mince. Efficace. Impressionnant. Quelqu'un comme les programmeurs Rockstar que réclament ces offres d'emploi, mais avec des côtelettes de leader. Ainsi, lorsque votre équipe écrit du code, vous l'encouragez à le faire correctement du premier coup (même si « correct » est un terme très relatif, ici). Ils savent que c'est la voie du Clever Coder, et aussi la voie de ceux qui n'ont pas besoin de perdre du temps à refactoriser plus tard.

Je sens ça. La force du perfectionnisme est parfois forte en moi aussi. Vous voulez que votre équipe passe un peu de temps maintenant pour gagner beaucoup de temps plus tard, parce que tout le monde a parcouru sa part de "Shitty Code Other People Wrote (What the Hell Were They Thinking?)". C'est SCOPWWHWTT en abrégé, car je sais que vous aimez les acronymes imprononçables.

Je sais aussi que vous ne voulez pas que le code de votre équipe soit celui d'eux-mêmes ou de n'importe qui d'autre sur toute la ligne.

Voyons donc ce qui peut être fait pour guider votre équipe dans la bonne direction.

Quoi optimiser : Bienvenue dans This Being an Art

Tout d'abord, lorsque nous pensons à l'optimisation des programmes, nous supposons souvent immédiatement que nous parlons de performances. Même cela est déjà plus vague qu'il n'y paraît (vitesse ? utilisation de la mémoire ? etc.) alors arrêtons-nous là.

Rendons-le encore plus ambigu ! Juste au début.

Mon cerveau de toile d'araignée aime créer de l'ordre dans la mesure du possible, il me faudra donc chaque once d'optimisme pour considérer ce que je m'apprête à dire comme une bonne chose .

Il existe une règle simple d'optimisation (des performances) qui dit Ne le faites pas . Cela semble assez facile à suivre de manière rigide, mais tout le monde n'est pas d'accord avec cela. Je ne suis pas non plus tout à fait d'accord avec ça. Certaines personnes écriront simplement un meilleur code dès le départ que d'autres. Espérons que pour une personne donnée, la qualité du code qu'elle écrirait dans un tout nouveau projet s'améliorera généralement avec le temps. Mais je sais que, pour de nombreux programmeurs, ce ne sera pas le cas, car plus ils en sauront, plus ils seront tentés d'optimiser prématurément.

Pour de nombreux programmeurs… plus ils en savent, plus ils seront tentés d'optimiser prématurément.

Donc, ce Ne le faites pas ne peut pas être une science exacte, mais est uniquement destiné à contrecarrer l'envie intérieure du technicien typique de résoudre le puzzle. Ceci, après tout, est ce qui attire de nombreux programmeurs vers le métier en premier lieu. Je comprends. Mais demandez-leur de le sauver , de résister à la tentation. Si l'on a besoin d'un point de vente pour résoudre des énigmes en ce moment , on peut toujours se plonger dans le Sudoku du journal du dimanche, ou prendre un livre Mensa, ou aller coder au golf avec un problème artificiel. Mais laissez-le hors du repo jusqu'au bon moment. C'est presque toujours une voie plus sage que la pré-optimisation.

N'oubliez pas que cette pratique est suffisamment notoire pour que les gens se demandent si l'optimisation prématurée est la racine de tous les maux. (Je n'irais pas aussi loin, mais je suis d'accord avec le sentiment.)

Je ne dis pas que nous devrions choisir la manière la plus insensée à laquelle nous pouvons penser à tous les niveaux de conception. Bien sûr que non. Mais au lieu de choisir les plus intelligents, nous pouvons considérer d'autres valeurs :

  1. Le plus simple à expliquer à votre nouvelle recrue
  2. Les plus susceptibles de passer une revue de code par votre développeur le plus expérimenté
  3. Le plus maintenable
  4. Le plus rapide à écrire
  5. Le plus simple à tester
  6. Le plus portable
  7. etc.

Mais c'est là que le problème se révèle difficile. Il ne s'agit pas seulement d'éviter d'optimiser la vitesse, la taille du code, l'empreinte mémoire, la flexibilité ou la pérennité. C'est une question d'équilibre et de savoir si ce que vous faites est réellement conforme à vos valeurs et à vos objectifs. C'est entièrement contextuel, et parfois même impossible à mesurer objectivement.

C'est un art. (Cf L'art de la programmation informatique.)

Pourquoi est-ce une bonne chose ? Parce que la vie est comme ça. C'est désordonné. Nos cerveaux orientés vers la programmation veulent parfois tellement mettre de l'ordre dans le chaos que nous finissons ironiquement par multiplier le chaos. C'est comme le paradoxe d'essayer de forcer quelqu'un à vous aimer. Si vous pensez y avoir réussi, ce n'est plus de l'amour; pendant ce temps, vous êtes accusé de prise d'otage, vous avez probablement besoin de plus d'amour que jamais, et cette métaphore doit être l'une des plus maladroites que j'aurais pu choisir.

Quoi qu'il en soit, si vous pensez avoir trouvé le système parfait pour quelque chose, eh bien… profitez de l'illusion pendant qu'elle dure, je suppose. Ce n'est pas grave, les échecs sont de merveilleuses occasions d'apprendre.

Une optimisation prématurée complique souvent inutilement et inutilement votre produit.

Gardez l'UX à l'esprit

Explorons comment l'expérience utilisateur s'inscrit parmi ces priorités potentielles. Après tout, même vouloir que quelque chose fonctionne bien est, à un certain niveau, une question d'UX.

Si vous travaillez sur une interface utilisateur, quel que soit le framework ou le langage utilisé par le code, il y aura une certaine quantité de passe-partout et de répétition. Il peut certainement être utile en termes de temps pour le programmeur et de clarté du code d'essayer de réduire cela. Pour aider à l'art d'équilibrer les priorités, je veux partager quelques histoires.

À un emploi, l'entreprise pour laquelle je travaillais utilisait un système d'entreprise à source fermée basé sur une pile technologique opiniâtre. En fait, il était si opiniâtre que le fournisseur qui nous l'a vendu a refusé de faire des personnalisations de l'interface utilisateur qui ne correspondaient pas aux opinions de la pile, car c'était si pénible pour leurs développeurs. Je n'ai jamais utilisé leur pile, donc je ne les condamne pas pour cela, mais le fait est que ce compromis "bon pour le programmeur, mauvais pour l'utilisateur" était si lourd pour mes collègues dans certains contextes que j'ai mis fin jusqu'à écrire un module complémentaire tiers réimplémentant cette partie de l'interface utilisateur du système. (C'était un énorme booster de productivité. Mes collègues ont adoré ! Plus d'une décennie plus tard, cela fait toujours gagner du temps et de la frustration à tout le monde.)

Je ne dis pas que l'opinion est un problème en soi ; juste que trop de cela est devenu un problème dans notre cas. À titre de contre-exemple, l'un des grands atouts de Ruby on Rails est précisément qu'il est opiniâtre, dans un écosystème frontal où l'on a facilement le vertige d'avoir trop d'options. (Donnez-moi quelque chose avec des opinions jusqu'à ce que je puisse trouver la mienne !)

En revanche, vous pourriez être tenté de couronner UX le roi de tout dans votre projet. Un objectif louable, mais permettez-moi de raconter ma deuxième histoire.

Quelques années après le succès du projet ci-dessus, un de mes collègues est venu me voir pour me demander d'optimiser l'UX en automatisant un certain scénario désordonné de la vie réelle qui se présentait parfois, afin qu'il puisse être résolu en un seul clic. J'ai commencé à analyser s'il était même possible de concevoir un algorithme qui n'aurait pas de faux positifs ou négatifs en raison des nombreux et étranges cas extrêmes du scénario. Plus j'en parlais avec mon collègue, plus je réalisais que les exigences n'allaient tout simplement pas payer. Le scénario ne se présentait qu'une fois de temps en temps - par mois, disons - et prenait actuellement quelques minutes à une personne pour le résoudre. Même si nous pouvions l'automatiser avec succès, sans aucun bogue, il faudrait des siècles pour que le temps de développement et de maintenance requis soit amorti en termes de temps économisé par mes collègues. Le plaisir des gens en moi a eu un moment difficile à dire «non», mais j'ai dû écourter la conversation.

Laissez donc l'ordinateur faire ce qu'il peut pour aider l'utilisateur, mais seulement dans une mesure raisonnable. Comment savez-vous dans quelle mesure c'est?

Ce qui est facile et difficile pour les ordinateurs

Une approche que j'aime adopter consiste à profiler l'UX comme vos développeurs profilent leur code. Découvrez auprès de vos utilisateurs où ils passent le plus de temps à cliquer ou à taper la même chose encore et encore, et voyez si vous pouvez optimiser ces interactions. Votre code peut-il faire des suppositions éclairées sur ce qu'il va probablement entrer et en faire une valeur par défaut sans entrée? Mis à part certains contextes interdits (confirmation CLUF sans clic ?), cela peut vraiment faire une différence pour la productivité et le bonheur de vos utilisateurs. Faites des tests d'utilisabilité dans les couloirs si vous le pouvez. Parfois, vous pouvez avoir du mal à expliquer ce qui est facile pour les ordinateurs et ce qui ne l'est pas… mais dans l'ensemble, cette valeur est susceptible d'être d'une importance assez élevée pour vos utilisateurs.

Éviter l'optimisation prématurée : quand et comment optimiser

Nonobstant notre exploration d'autres contextes, supposons maintenant explicitement que nous optimisons certains aspects des performances de la machine brute pour le reste de cet article. Mon approche suggérée s'applique également à d'autres cibles, comme la flexibilité, mais chaque cible aura ses propres pièges ; le point principal est que l'optimisation prématurée de quoi que ce soit échouera probablement.

Alors, en termes de performances, quelles sont les méthodes d'optimisation à suivre ? Creusons.

Ce n'est pas une initiative populaire, c'est triple-eh

Le TL;DR est : Travaillez du haut vers le bas. Les optimisations de niveau supérieur peuvent être effectuées plus tôt dans le projet, et celles de niveau inférieur doivent être laissées pour plus tard. C'est tout ce dont vous avez besoin pour comprendre le sens de l'expression « optimisation prématurée » ; faire les choses dans cet ordre a de fortes chances de faire perdre du temps à votre équipe et d'être contre-efficace. Après tout, vous n'écrivez pas tout le projet en code machine dès le départ, n'est-ce pas ? Notre modus operandi AAA consiste donc à optimiser dans cet ordre :

  1. Architecture
  2. Algorithmes
  3. Assemblée

La sagesse populaire veut que les algorithmes et les structures de données soient souvent les endroits les plus efficaces pour optimiser, du moins en ce qui concerne les performances. Gardez à l'esprit, cependant, que l'architecture détermine parfois quels algorithmes et structures de données peuvent être utilisés.

Une fois, j'ai découvert un logiciel réalisant un rapport financier en interrogeant plusieurs fois une base de données SQL pour chaque transaction financière, puis en effectuant un calcul très basique côté client. Il n'a fallu à la petite entreprise utilisant le logiciel que quelques mois d'utilisation avant que même sa quantité relativement faible de données financières ne signifie qu'avec des ordinateurs de bureau flambant neufs et un serveur assez costaud, le temps de génération de rapports atteignait déjà plusieurs minutes, et c'était celui qu'ils devaient utiliser assez fréquemment. J'ai fini par écrire une instruction SQL simple qui contenait la logique de sommation - contrecarrant leur architecture en déplaçant le travail sur le serveur pour éviter toutes les duplications et allers-retours sur le réseau - et même plusieurs années de données plus tard, ma version pouvait générer le même rapport en quelques millisecondes sur le même ancien matériel.

Parfois, vous n'avez pas d'influence sur l'architecture d'un projet car il est trop tard dans le projet pour qu'un changement d'architecture soit réalisable. Parfois, vos développeurs peuvent le contourner comme je l'ai fait dans l'exemple ci-dessus. Mais si vous êtes au début d'un projet et que vous avez votre mot à dire sur son architecture, il est maintenant temps de l'optimiser.

Architecture

"Si les constructeurs construisaient des bâtiments de la même manière que les programmeurs écrivaient des programmes, alors le premier pic qui arriverait détruirait la civilisation." --Loi de Weinberg

Dans un projet, l'architecture est la partie la plus coûteuse à modifier après coup, c'est donc un endroit où il peut être judicieux d'optimiser au début. Si votre application doit fournir des données via des autruches, par exemple, vous souhaiterez la structurer en paquets basse fréquence et à charge utile élevée pour éviter d'aggraver encore un mauvais goulot d'étranglement. Dans ce cas, vous feriez mieux d'avoir une implémentation complète de Tetris pour divertir vos utilisateurs, car un spinner de chargement ne va tout simplement pas le couper. (Blague à part : il y a des années, j'installais ma première distribution Linux, Corel Linux 2.0, et j'étais ravi que le long processus d'installation inclue exactement cela. Ayant vu les écrans d'infopublicité du programme d'installation de Windows 95 tellement de fois que je les avais était une bouffée d'air frais à l'époque.)

À titre d'exemple de changement d'architecture coûteux, la raison pour laquelle le rapport SQL susmentionné est si hautement non évolutif en premier lieu ressort clairement de son historique. L'application a évolué au fil du temps, depuis ses racines dans MS-DOS et une base de données personnalisée et maison qui n'était même pas multi-utilisateurs à l'origine. Lorsque le fournisseur est finalement passé à SQL, le schéma et le code de génération de rapports semblent avoir été portés un pour un. Cela leur a laissé des années d'améliorations impressionnantes des performances de plus de 1 000 % à saupoudrer dans leurs mises à jour, chaque fois qu'ils ont terminé le changement d'architecture en utilisant réellement les avantages de SQL pour un rapport donné. Bon pour les affaires avec des clients enfermés comme mon employeur d'alors, et essayant clairement de donner la priorité à l'efficacité du codage lors de la transition initiale. Mais répondre aux besoins des clients, dans certains cas, à peu près aussi efficacement qu'un marteau tourne une vis.

L'architecture consiste en partie à anticiper dans quelle mesure votre projet devra pouvoir évoluer et de quelle manière. Parce que l'architecture est de si haut niveau, il est difficile de concrétiser nos « choses à faire et à ne pas faire » sans nous concentrer sur des technologies et des domaines spécifiques.

Je ne l'appellerais pas comme ça, mais tout le monde le fait

Heureusement, Internet regorge de sagesse recueillie sur la plupart des types d'architecture jamais imaginés. Lorsque vous savez qu'il est temps d'optimiser votre architecture, la recherche des pièges se résume à trouver le mot à la mode qui décrit votre brillante vision. Il y a de fortes chances que quelqu'un ait pensé dans le même sens que vous, l'ait essayé, échoué, itéré et publié à ce sujet dans un blog ou un livre.

L'identification des mots à la mode peut être difficile à accomplir simplement en recherchant, car pour ce que vous appelez un FLDSMDFR, quelqu'un d'autre a déjà inventé le terme SCOPWWHWTT, et ils décrivent le même problème que vous résolvez, mais en utilisant un vocabulaire complètement différent que vous le feriez. Les communautés de développeurs à la rescousse ! Lancez StackExchange ou HashNode avec une description aussi complète que possible, ainsi que tous les mots à la mode que votre architecture n'est pas , afin qu'ils sachent que vous avez fait suffisamment de recherches préliminaires. Quelqu'un se fera un plaisir de vous éclairer.

En attendant, quelques conseils généraux pourraient être une bonne matière à réflexion.

Algorithmes et assemblage

Avec une architecture propice, c'est ici que les codeurs de votre équipe obtiendront le plus de T-bling pour leur temps. L'évitement de base de l'optimisation prématurée s'applique ici aussi, mais vos programmeurs feraient bien de considérer certaines des spécificités à ce niveau. Il y a tellement de choses à penser en ce qui concerne les détails de mise en œuvre que j'ai écrit un article séparé sur l'optimisation du code destiné aux codeurs de première ligne et seniors.

Mais une fois que vous et votre équipe avez implémenté quelque chose de non optimisé en termes de performances, le laissez-vous vraiment à Ne pas le faire ? Vous n'optimisez jamais ?

Tu as raison. La règle suivante est, réservée aux experts, ne le faites pas encore .

Il est temps de se comparer !

Votre code fonctionne. C'est peut-être si lent que vous savez déjà que vous devrez optimiser, car c'est du code qui s'exécutera souvent. Peut-être que vous n'êtes pas sûr, ou que vous avez un algorithme O(n) et que vous pensez que c'est probablement bon. Quel que soit le cas, si cet algorithme mérite d'être optimisé, ma recommandation à ce stade est la même : exécutez un test de référence simple.

Pourquoi? N'est-il pas clair que mon algorithme O(n³) ne peut pas être pire qu'autre chose ? Eh bien, pour deux raisons :

  1. Vous pouvez ajouter le benchmark à votre suite de tests, en tant que mesure objective de vos objectifs de performance, qu'ils soient ou non atteints actuellement.
  2. Même les experts peuvent par inadvertance ralentir les choses. Même quand cela paraît évident. Vraiment évident.

Vous ne me croyez pas sur ce deuxième point ?

Comment obtenir de meilleurs résultats avec du matériel à 1 400 $ qu'avec du matériel à 7 000 $

Jeff Atwood de la renommée de StackOverflow a un jour souligné qu'il peut parfois (généralement, à son avis) être plus rentable d'acheter simplement un meilleur matériel que de consacrer un temps précieux de programmeur à l'optimisation. OK, alors supposons que vous êtes parvenu à une conclusion raisonnablement objective que votre projet correspondrait à ce scénario. Supposons en outre que ce que vous essayez d'optimiser est le temps de compilation, car c'est un projet Swift lourd sur lequel vous travaillez, et cela est devenu un goulot d'étranglement assez important pour les développeurs. Temps d'achat de matériel !

Que devriez-vous acheter ? Eh bien, évidemment, yen pour yen, le matériel plus cher a tendance à mieux fonctionner que le matériel moins cher. Donc, évidemment, un Mac Pro à 7 000 $ devrait compiler votre logiciel plus rapidement que certains Mac Mini de milieu de gamme, n'est-ce pas ?

Tort!

Il s'avère que parfois plus de cœurs signifient une compilation plus efficace… et dans ce cas particulier, LinkedIn a découvert à la dure que le contraire est vrai pour leur pile.

Mais j'ai vu la direction commettre une erreur supplémentaire : elle n'a même pas effectué de benchmark avant et après, et a constaté qu'une mise à niveau matérielle ne rendait pas son logiciel plus rapide. Mais il n'y avait aucun moyen de savoir avec certitude; et de plus, ils n'avaient toujours aucune idée de l'endroit où se trouvait le goulot d'étranglement, ils sont donc restés mécontents de la performance, ayant utilisé le temps et l'argent qu'ils étaient prêts à allouer au problème.

OK, j'ai déjà fait un benchmark. Puis-je réellement optimiser encore ??

Oui, en supposant que vous avez décidé que vous en avez besoin. Mais peut-être que cette décision attendra que plus/tous les autres algorithmes soient également implémentés, afin que vous puissiez voir comment les pièces mobiles s'emboîtent et lesquelles sont les plus importantes via le profilage. Cela peut être au niveau de l'application pour une petite application, ou cela peut ne s'appliquer qu'à un seul sous-système. Quoi qu'il en soit, rappelez-vous qu'un algorithme particulier peut sembler important pour l'application globale, mais même les experts, en particulier les experts, ont tendance à mal diagnostiquer cela.

Réfléchissez avant de déranger

"Je ne sais pas pour vous, mais..."

Gavin Belson de la Silicon Valley a déclaré : "Je ne veux pas vivre dans un monde où quelqu'un d'autre rend le monde meilleur... meilleur que nous."

En guise de dernier élément de réflexion, réfléchissez à la manière dont vous pouvez appliquer l'idée de fausse optimisation à une vision beaucoup plus large : votre projet ou votre entreprise elle-même, ou même un secteur de l'économie.

Je sais, il est tentant de penser que la technologie sauvera la situation et que nous pouvons être les héros qui y parviendront.

De plus, si nous ne le faisons pas, quelqu'un d'autre le fera.

Mais rappelez-vous que le pouvoir corrompt, malgré les meilleures intentions. Je ne créerai pas de lien vers des articles en particulier ici, mais si vous n'en avez parcouru aucun, cela vaut la peine d'en savoir plus sur l'impact plus large de la perturbation de l'économie et à qui cela sert parfois en fin de compte. Vous pourriez être surpris de certains des effets secondaires d'essayer de sauver le monde grâce à l'optimisation.

Post-scriptum

As-tu remarqué quelque chose, Optimus ? La seule fois où je t'ai appelé Optimus, c'était au début et maintenant à la fin. Vous n'étiez pas appelé Optimus tout au long de l'article. Je vais être honnête, j'ai oublié. J'ai écrit tout l'article sans vous appeler Optimus. À la fin, quand j'ai réalisé que je devais revenir en arrière et saupoudrer ton nom dans tout le texte, une petite voix en moi m'a dit, ne le fais pas .