Je suis un développeur de logiciels junior et je me demandais quel serait le meilleur moment pour optimiser un logiciel pour de meilleures performances (vitesse).
En supposant que le logiciel n'est pas extrêmement volumineux et complexe à gérer, vaut-il mieux passer plus de temps au début à l'optimiser ou dois-je simplement développer le logiciel qui exécute correctement toutes les fonctionnalités et ensuite procéder à l'optimisation pour de meilleures performances?
La première chose devrait toujours et toujours être la lisibilité. Si c'est lent mais lisible, je peux le réparer. S'il est cassé mais lisible, je peux le réparer. Si c'est illisible, je dois demander à quelqu'un d'autre ce que cela était même censé faire.
Il est remarquable de voir à quel point votre code peut être performant lorsque vous étiez uniquement concentré sur la lisibilité. Tant et si bien que j'ignore généralement les performances jusqu'à ce qu'on me donne une raison de s'en soucier. Cela ne devrait pas être pris pour signifier que je ne me soucie pas de la vitesse. Je fais. Je viens de découvrir qu'il y a très peu de problèmes dont les solutions sont en fait plus rapides lorsqu'elles sont difficiles à lire.
Seules deux choses me sortent de ce mode:
Dans tous les cas, évitez paralysie d'analyse en vous faisant penser que vous ne devriez pas essayer une solution car elle n'est peut-être pas la plus rapide. Votre code bénéficiera réellement si vous essayez plusieurs solutions, car les modifications vous obligeront à utiliser une conception qui facilite la modification. Une base de code flexible peut être créée plus rapidement plus tard là où elle en a vraiment besoin. Choisissez une vitesse plus flexible et vous pouvez choisir la vitesse dont vous avez besoin.
Si un certain niveau de performance est nécessaire (une exigence non fonctionnelle), cela devrait être un objectif de conception dès le départ. Par exemple. cela peut influencer les technologies qui pourraient être appropriées ou la façon dont vous structurez le flux de données dans le programme.
Mais en général, il n'est pas possible d'optimiser avant que le code ne soit écrit: faites-le d'abord fonctionner, puis faites-le bien et, enfin, faites-le rapidement .
Un gros problème avec l'optimisation avant d'implémenter la plupart des fonctionnalités est que vous vous êtes enfermé dans des décisions de conception sous-optimales aux mauvais endroits. Il y a souvent (mais pas nécessairement) un compromis entre la maintenabilité et les performances. La plupart des parties de votre programme ne sont absolument pas pertinentes pour les performances! Les programmes typiques n'ont que quelques points chauds qui méritent vraiment d'être optimisés. Donc, sacrifier la maintenabilité pour la performance dans tous les endroits qui n'ont pas besoin de performance est un très mauvais métier.
Optimiser la maintenabilité est la meilleure approche. Si vous dépensez votre intelligence sur la maintenabilité et les conceptions claires, il vous sera plus facile à long terme d'identifier les sections critiques et de les optimiser en toute sécurité sans compromettre la conception globale.
quel serait le meilleur moment pour optimiser un logiciel pour de meilleures performances (vitesse).
Commencez par supprimer de votre esprit le concept que la performance est la même chose que la vitesse. La performance est ce que l'utilisateur pense que la performance est.
Si vous faites répondre une application deux fois plus vite à un clic de souris et que vous passez de dix microsecondes à cinq microsecondes, l'utilisateur s'en fiche. Si vous faites réagir une application deux fois plus vite à un clic de souris et que vous passez de quatre mille ans à deux mille ans, encore une fois, l'utilisateur s'en fiche.
Si vous rendez votre application deux fois plus rapide et que vous utilisez toute la mémoire de la machine et que vous plantez, l'utilisateur s'en fiche qu'il soit désormais deux fois plus rapide.
La performance est la science qui consiste à faire des compromis efficaces sur la consommation de ressources pour obtenir une expérience utilisateur particulière. Le temps de l'utilisateur est une ressource importante, mais ce n'est jamais juste "plus rapide". Atteindre les objectifs de performances nécessite presque toujours des compromis, et ils échangent souvent du temps contre de l'espace ou vice versa.
En supposant que le logiciel n'est pas extrêmement volumineux et complexe à gérer
C'est une hypothèse terrible.
Si le logiciel n'est pas volumineux et complexe à gérer, il ne résout probablement pas un problème intéressant qui intéresse un utilisateur, et il est probablement très facile à optimiser.
vaut-il mieux passer plus de temps au début à l'optimiser ou devrais-je simplement développer le logiciel qui exécute toutes les fonctionnalités correctement puis procéder à l'optimisation pour de meilleures performances?
Vous êtes assis là sur une page vierge et vous écrivez void main() {}
Commencez-vous à optimiser? Il n'y a rien à optimiser! Le bon ordre est:
Si vous essayez de le faire dans un autre ordre, vous vous retrouvez avec un mauvais code qui est un gâchis, et maintenant vous avez un programme qui produit des réponses erronées très rapidement et résiste aux changements.
Mais il manque une étape. Le bon ordre réel est:
En règle générale, il est préférable d'optimiser les performances plus tard, mais j'ai vu de nombreux projets se détériorer lorsque les développeurs se rendent compte qu'ils se sont retrouvés avec un logiciel qui ralentit lorsqu'une charge ou des données importantes y sont ajoutées.
Donc, une approche intermédiaire serait la meilleure à mon avis; n'y mettez pas trop l'accent, mais ne négligez pas complètement les performances.
Je vais donner un exemple que j'ai vu plusieurs fois; étant donné une bibliothèque ORM, nous avons une entité utilisateur qui peut avoir une ou plusieurs commandes. Passons en revue toutes les commandes d'un utilisateur et découvrons le montant que l'utilisateur a dépensé dans notre magasin - une approche naïve:
User user = getUser();
int totalAmount;
for (Order o : user.getOrders()) {
totalAmount += o.getTotalAmount();
}
J'ai vu des développeurs écrire des choses similaires, sans penser aux implications; nous obtenons d'abord l'utilisateur, qui, espérons-le, ne sera qu'une requête SQL sur la table User (mais peut impliquer beaucoup, beaucoup plus), puis nous parcourons les commandes, ce qui peut inclure l'obtention de toutes les données pertinentes pour toutes les lignes de commande de la commande , informations sur les produits, etc. - tout cela pour obtenir un seul entier pour chaque commande!
Le nombre de requêtes SQL ici pourrait vous surprendre. Bien sûr, cela dépend de la façon dont vos entités sont structurées.
Ici, l'approche correcte serait très probablement d'ajouter une fonction distincte pour obtenir la somme de la base de données via une requête distincte écrite dans le langage de requête fourni par l'ORM, et je recommanderais de le faire la première fois, et ne pas reporter cela pour plus tard; car si vous le faites, vous vous retrouverez probablement avec beaucoup plus de problèmes à régler et vous ne saurez pas par où commencer.
La performance totale du système est le produit des interactions complexes de la totalité des composants du système. C'est un système non linéaire. Par conséquent, les performances seront déterminées non seulement par les performances individuelles des composants, mais aussi par goulots d'étranglement entre eux.
De toute évidence, vous ne pouvez pas tester les goulots d'étranglement si tous les composants de votre système ne sont pas encore construits, vous ne pouvez donc pas vraiment tester très bien au début. D'un autre côté, une fois le système construit, vous ne trouverez peut-être pas si facile d'apporter les modifications nécessaires pour obtenir les performances souhaitées. Donc, c'est une bonne foi Catch-22 .
Pour rendre les choses plus difficiles, votre profil de performances peut changer radicalement lorsque vous passez à un environnement de type production, qui n'est souvent pas disponible au début.
Donc que fais-tu? Eh bien, quelques petites choses.
Soyez pragmatique. Au début, vous pouvez choisir d'utiliser des fonctionnalités de plate-forme qui sont les "meilleures pratiques" pour les performances; par exemple, utilisez le regroupement de connexions, les transactions asynchrones et évitez le statu quo, ce qui peut être la mort d'une application multithread où différents travailleurs se disputent l'accès aux données partagées. Normalement, vous ne testeriez pas ces modèles pour les performances, vous savez simplement par expérience ce qui fonctionne bien.
Soyez itératif. Prenez des mesures de performances de base lorsque le système est relativement nouveau et refaites des tests de temps en temps pour vous assurer que le code nouvellement introduit n'a pas trop dégradé les performances.
N'optimisez pas trop tôt. Vous ne savez jamais ce qui va être important et ce qui ne compte pas; un algorithme d'analyse de chaîne ultra-rapide peut ne pas aider si votre programme attend constamment des E/S, par exemple.
Dans les applications Web en particulier, vous pouvez vous concentrer non pas tant sur les performances que sur l'évolutivité. Si l'application peut évoluer, les performances n'ont presque pas d'importance, car vous pouvez continuer à ajouter des nœuds à votre batterie jusqu'à ce qu'elle soit suffisamment rapide.
Une attention particulière est portée à la base de données. En raison des contraintes d'intégrité transactionnelle, la base de données a tendance à être un goulot d'étranglement qui domine chaque partie du système. Si vous avez besoin d'un système hautes performances, assurez-vous que des personnes talentueuses travaillent du côté de la base de données, examinent les plans de requête et développent des structures de table et d'index qui rendront les opérations courantes aussi efficaces que possible.
La plupart de ces activités ne concernent pas le début ou la fin du projet mais doivent être suivies en continu.
Je pourrais être partisan de travailler dans des domaines très critiques pour les performances comme le traitement d'image et le lancer de rayons, mais je dirais quand même d'optimiser "le plus tard possible". Quel que soit le niveau de performance de vos besoins, il y a toujours beaucoup plus d'informations et de clarté avec le recul, après avoir mesuré, qu'avant, ce qui signifie que même les optimisations les plus efficaces sont généralement appliquées plus tard après avoir acquis ces connaissances.
Cas particuliers
Mais parfois "aussi tard que possible" est encore assez sacrément au début quelques cas particuliers. Si nous parlons de rendus hors ligne, par exemple, les structures de données et les techniques que vous utilisez pour atteindre les performances s'infiltrent réellement dans la conception utilisateur. Cela peut sembler dégoûtant, mais le domaine est si avant-gardiste et si critique pour les performances que les utilisateurs acceptent les contrôles utilisateur spécifiques aux techniques d'optimisation applicables à un raytracer particulier (ex: mise en cache de l'irradiance ou cartographie des photons), car certains d'entre eux sont utilisés aux heures d'attente pour le rendu d'une image, et d'autres sont habitués à débourser d'énormes sommes d'argent pour louer ou posséder une ferme de rendu avec des machines dédiées au rendu. Il y a une réduction massive de temps et d'argent pour ces utilisateurs si un moteur de rendu hors ligne compétitif peut offrir une réduction non triviale du temps passé à rendre. C'est une sorte de domaine où une réduction de 5% du temps excite réellement les utilisateurs.
Dans de tels cas particuliers, vous ne pouvez pas choisir une technique de rendu à volonté et espérer l'optimiser plus tard, car la conception entière, y compris la conception utilisateur, tourne autour des structures de données et des algorithmes que vous utilisez. Vous ne pouvez pas nécessairement même vous contenter de ce qui a bien fonctionné pour d'autres personnes, car ici, vous, en tant qu'individu, et vos forces et faiblesses particulières, contribuez fortement à fournir une solution compétitive. L'état d'esprit et la sensibilité du développeur principal derrière Arnold sont différents de ceux travaillant sur VRay qui ont utilisé une approche très différente; ils ne peuvent pas nécessairement échanger des lieux/techniques et faire le meilleur travail (même s'ils sont tous les deux des leaders industriels). Vous devez genre d'expérimentation et de prototype et de référence et trouver ce que vous êtes particulièrement bon à faire étant donné l'éventail infini de techniques de pointe là-bas si vous espérez expédier quelque chose de compétitif qui se vendra réellement. Donc, dans ce cas particulier, les problèmes de performances remontent au premier plan comme peut-être la préoccupation la plus importante avant même de commencer le développement.
Pourtant, ce n'est pas nécessairement une violation de l'optimisation "aussi tard que possible", c'est juste "aussi tard que possible" est plutôt tôt dans ces cas extrêmes et particuliers . Déterminer quand et aussi ce qui n'a pas besoin de problèmes de performances aussi précoces, voire jamais, est probablement le principal défi pour le développeur. Ce qu'il ne faut pas optimiser pourrait être l'une des choses les plus précieuses à apprendre et à continuer à apprendre dans la carrière d'un développeur, car vous ne pouvez pas manquer de développeurs naïfs qui veulent tout optimiser (et malheureusement, même certains vétérans qui ont réussi à garder leur travail d'une manière ou d'une autre) malgré leur contre-productivité).
Aussi tard que possible
La partie la plus difficile est peut-être d'essayer de comprendre ce que cela signifie. J'apprends toujours et je programme depuis près de trois décennies. Mais surtout maintenant dans ma troisième décennie, je commence à réaliser que ce n'est pas si difficile. Ce n'est pas sorcier, si vous vous concentrez davantage sur la conception que sur la mise en œuvre. Plus vos conceptions laissent une marge de manœuvre pour les optimisations appropriées plus tard sans modifications de la conception, plus tard vous pourrez optimiser. Et la productivité de plus en plus que j'ai gagné en recherchant de tels modèles qui me permettent de respirer.
Conception qui offre une marge respiratoire à optimiser plus tard
Ces types de conceptions ne sont en fait pas si difficiles à réaliser dans la plupart des cas si nous pouvons appliquer un certain "bon sens". En tant qu'histoire personnelle, je suis dans les arts visuels en tant que passe-temps (je trouve qu'il est quelque peu utile de programmer des logiciels pour les artistes étant un peu moi-même pour comprendre leurs besoins et parler leur langue), et j'ai passé du temps au début des années 2000 à utiliser des applets Oekaki en ligne comme un moyen rapide de griffonner et de partager mon travail et de se connecter avec d'autres artistes.
En particulier, mon site préféré et mon applet y étaient criblés de défauts de performances (toute taille de pinceau non triviale ralentirait à une exploration), mais avait une communauté très agréable. Pour contourner les problèmes de performances, j'ai utilisé de minuscules pinceaux 1 ou 2 pixels et j'ai simplement griffonné mon travail comme suit:
Pendant ce temps, je continuais à donner à l'auteur du logiciel des suggestions pour améliorer les performances, et il a remarqué que mes suggestions étaient de nature particulièrement technique en ce qui concerne les optimisations de la mémoire et les algorithmes, etc. Il m'a donc demandé si j'étais programmeur et j'ai dit oui et il m'a invité à travailler sur le code source.
J'ai donc regardé le code source, l'ai exécuté, profilé, et à ma grande horreur, il avait conçu le logiciel autour du concept d'une "interface de pixels abstraits", comme IPixel
, qui a fini par être la cause profonde de la les meilleurs hotspots pour tout avec des allocations dynamiques et une répartition pour chaque pixel de chaque image. Pourtant, il n'y avait aucun moyen pratique d'optimiser cela sans reconsidérer la conception de l'ensemble du logiciel, car la conception l'avait piégé dans un coin où il n'y a pas grand-chose au-delà de la plus triviale des micro-optimisations lorsque nos abstractions fonctionnent au niveau granulaire d'un seul pixel abstrait. et tout dépend de ce pixel abstrait. Et donc nous avons abandonné l'idée d'optimiser le logiciel pour gérer de plus gros pinceaux et filtres en temps réel et ainsi de suite et je suis retourné au doodling avec des pinceaux 1 ou 2 pixels.
Je pense que c'est une violation du "bon sens", mais ce n'était manifestement pas un tel bon sens pour le développeur. Mais c'est comme ne pas abstraire des choses à un niveau si granulaire où même les cas d'utilisation les plus élémentaires vont être instanciés par millions, comme avec des pixels ou des particules, ou de minuscules unités dans une simulation d'armée ginormous. Privilégiez le IImage
(vous pouvez gérer tous les formats d'image/pixel dont vous avez besoin à ce niveau d'agrégation plus volumineux) ou IParticleSystem
à IPixel
ou IParticle
, puis vous pouvez mettre en place les implémentations les plus basiques, les plus rapides à écrire et simples à comprendre derrière de telles interfaces et disposer de toute la marge de manœuvre dont vous aurez besoin pour optimiser plus tard sans reconsidérer la conception complète du logiciel.
Et c'est le but que je vois ces jours-ci. À l'exception des cas particuliers tels que les rendus hors ligne ci-dessus, concevez avec suffisamment de marge de manœuvre pour optimiser le plus tard possible, avec autant d'informations rétrospectives que possible (y compris les mesures), et appliquez toutes les optimisations nécessaires le plus tard possible.
Bien sûr, je ne suggère pas nécessairement de commencer par utiliser des algorithmes de complexité quadratique sur des entrées qui atteignent facilement une taille non triviale dans les cas d'utilisation courants. Qui fait ça de toute façon? Mais je ne pense même pas que ce soit un gros problème si la mise en œuvre est facile à échanger plus tard. Ce n'est toujours pas une grave erreur si vous n'avez pas à reconsidérer vos conceptions.
Il est plus facile d'écrire du code qui n'est ni porformant ni maintenable. Il est plus difficile d'écrire du code porformant. Il est encore plus difficile d'écrire du code maintenable. Et c'est le plus difficile à écrire du code qui soit à la fois maintenable et performant.
Maintenant, évidemment, cela dépend du type de système que vous créez, certains systèmes seront fortement critiques en termes de performances et auront besoin de ce qui est prévu dès le départ. Pour les personnes extrêmement talentueuses comme Eric Lippert, qui ont répondu ci-dessus, ces systèmes peuvent être courants; mais pour la plupart d'entre nous, ils sont la minorité des systèmes que nous construisons.
Cependant, étant donné l'état du matériel moderne, dans la majorité des systèmes, il n'est pas nécessaire d'accorder une attention particulière à l'optimisation dès le début, mais plutôt, éviter la destruction des performances est généralement suffisant. Ce que je veux dire par là, c'est d'éviter de faire des choses tout simplement stupides comme ramener tous les enregistrements d'une table pour obtenir un compte au lieu de simplement interroger select count(*) from table
. Évitez simplement de faire des erreurs et faites un effort pour comprendre les outils que vous utilisez.
Ensuite, concentrez-vous d'abord sur la possibilité de rendre votre code maintenable. Par cela, je veux dire:
Le code maintenable est beaucoup plus facile à optimiser lorsque les statistiques montrent qu'il est nécessaire.
Ensuite, assurez-vous que votre code a [~ # ~] beaucoup [~ # ~] de tests automatisés, cela présente plusieurs avantages. Moins de bogues signifie plus de temps pour optimiser, en cas de besoin . De plus, lorsque vous optimisez, vous pouvez itérer et trouver la meilleure solution beaucoup plus rapidement car vous trouvez les bogues dans vos implémentations beaucoup plus rapidement.
Les scripts de déploiement automatisé et l'infrastructure scriptée sont également très utiles pour l'optimisation des performances, car là encore, ils vous permettent d'itérer plus rapidement; sans parler de ses autres avantages.
Je suis un développeur de logiciels junior et je me demandais quel serait le meilleur moment pour optimiser un logiciel pour de meilleures performances (vitesse).
Comprenez qu'il y a 2 extrêmes très différents.
Le premier extrême concerne les éléments qui affectent une grande partie de la conception, comme la façon de diviser le travail en combien de processus et/ou de threads et comment les éléments communiquent (sockets TCP/IP? Appels de fonction directs?), S'il faut implémenter un JIT avancé ou un interprète "un opcode à la fois", ou s'il faut planifier des structures de données pour qu'elles soient compatibles avec SIMD, ou ... Ces choses ont tendance à avoir une forte influence sur la mise en œuvre et deviennent excessivement difficiles/coûteuses à réajuster après.
L'autre extrême est les micro-optimisations - de minuscules petits ajustements partout. Ces choses ont tendance à n'avoir presque aucune influence sur l'implémentation (et il est souvent préférable de le faire par un compilateur de toute façon), et il est trivial de faire ces optimisations chaque fois que vous en avez envie.
Entre ces extrêmes se trouve une immense zone grise.
Ce qui se résume vraiment à l'expérience/aux suppositions éduquées utilisées pour répondre à une question "les avantages justifient-ils les coûts"? Pour des optimisations à/près d'un extrême, si vous vous trompez souvent, cela signifie rejeter tout votre travail et redémarrer à zéro ou l'échec du projet (trop de temps passé sur une conception inutilement trop compliquée). À/près de l'autre extrême, il est beaucoup plus judicieux de le laisser jusqu'à ce que vous soyez en mesure de prouver qu'il importe à l'aide de mesures (par exemple, le profilage).
Malheureusement, nous vivons dans un monde où beaucoup trop de gens pensent que l'optimisation ne comprend que les choses (pour la plupart non pertinentes) à l'extrême "triviale".
Cela dépend de ce que ces performances signifient pour votre application. Et s'il est même possible d'optimiser les performances avant que votre application ne soit fonctionnelle.
Le plus souvent, vous ne devez pas vous en préoccuper tant que vous n'avez rien de mieux à faire, mais il se peut qu'un certain niveau de performances soit essentiel au succès de votre application. Si tel était le cas et que vous suspectez que ce ne soit pas facile, vous devriez commencer à regarder les performances pour "échouer rapidement".
Un principe important pour tout projet est de se concentrer d'abord sur les parties dures. De cette façon, s'il s'avère que vous ne pouvez pas le faire, vous le saurez tôt et vous aurez le temps d'essayer quelque chose de totalement différent ou le projet peut être annulé avant que trop d'argent n'y soit consacré.
Je vais suggérer que la performance est plus que la vitesse. Il comprend l'échelle (des centaines à des milliers d'utilisateurs simultanés). Pour sûr, vous ne voulez pas que l'application se charge quand elle obtient une charge de production. Les performances incluent la quantité de ressources (par exemple la mémoire) que l'application consomme.
La performance est également une facilité d'utilisation. Certains utilisateurs préfèrent que 1 touche effectue une tâche en 10 secondes que 2 touches effectuent la tâche en 1 seconde. Pour des trucs comme ça, demandez à votre responsable de la conception. Je n'aime pas apporter des trucs comme ça aux utilisateurs tôt. Dans le vide, ils peuvent dire X mais une fois qu'ils travaillent avec une pré-version fonctionnelle, ils peuvent dire Y.
La meilleure vitesse individuelle consiste à conserver une ressource telle qu'une connexion à une base de données. Mais pour la mise à l'échelle, vous devez acquérir la connexion le plus tard possible et la libérer dès que possible. Un voyage dans la base de données pour obtenir 3 choses est plus rapide que 3 voyages distincts dans la base de données.
Faites-vous un voyage pour des informations qui ne changent pas pendant la session. Si c'est le cas, récupérez-le au début de la session et maintenez-le en mémoire.
Lors de la sélection du type de collection, tenez compte de la fonctionnalité, de la vitesse et de la taille.
Êtes-vous sûr de devoir conserver les articles dans une collection? Un problème courant consiste à lire toutes les lignes d'un fichier dans une liste, puis à traiter la liste une ligne à la fois. Il est beaucoup plus efficace de lire le fichier une ligne à la fois et de sauter la liste.
Bouclez-vous trois fois alors que vous pourriez boucler une fois et faire trois choses.
Y a-t-il un endroit où vous devrez peut-être traiter sur un autre thread avec un rappel. Si c'est le cas, empaquetez le code avec ce besoin possible à l'esprit s'il n'interfère pas avec les besoins de conception immédiats.
Beaucoup de performances sont également du code propre.
Il y a une optimisation prématurée et il suffit de faire des choses sensées à l'avance qui ne prennent pas vraiment plus de temps.
Dans la base de données, je vois une optimisation prématurée. Dérormalisera pour la vitesse avant qu'il y ait un problème de vitesse. L'argument que j'obtiens est que si nous changeons la table plus tard, nous devons tout changer. Souvent, vous pouvez créer une vue qui présente les données de cette façon et il peut être nécessaire de les échanger ultérieurement pour une table dénormalisée.
Obtenez d'abord le MVP au client. Faites ensuite un profilage statistique pour voir ce qui est lent.