Je parle de la façon dont nous écrivons des routines simples afin d'améliorer les performances sans rendre votre code plus difficile à lire ... par exemple, c'est le type que nous avons appris:
for(int i = 0; i < collection.length(); i++ ){
// stuff here
}
Mais, je le fais généralement quand un foreach
n'est pas applicable:
for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}
Je pense que c'est une meilleure approche car elle n'appellera la length
méthode qu'une seule fois ... ma petite amie dit que c'est cryptique cependant. Y a-t-il une autre astuce simple que vous utilisez sur vos propres développements?
code-quality
performance
Cristian
la source
la source
Réponses:
insérer une discussion prématurée est la racine de tous les maux
Cela dit, voici quelques habitudes que j'ai adoptées pour éviter une efficacité inutile et, dans certains cas, rendre mon code plus simple et plus correct également.
Ce n'est pas une discussion des principes généraux, mais de certaines choses à prendre en compte pour éviter d'introduire des inefficacités inutiles dans le code.
Connaissez votre big-O
Cela devrait probablement être fusionné dans la longue discussion ci-dessus. C'est à peu près le bon sens qu'une boucle à l'intérieur d'une boucle, où la boucle intérieure répète un calcul, va être plus lente. Par exemple:
Cela prendra un temps horrible si la chaîne est vraiment longue, car la longueur est recalculée à chaque itération de la boucle. Notez que GCC optimise réellement ce cas car il
strlen()
est marqué comme une fonction pure.Lors du tri d'un million d'entiers 32 bits, le tri à bulles serait la mauvaise façon de procéder . En général, le tri peut être effectué en temps O (n * log n) (ou mieux, dans le cas du tri radix), donc à moins que vous ne sachiez que vos données seront petites, recherchez un algorithme qui est au moins O (n * log n).
De même, lorsque vous traitez avec des bases de données, faites attention aux index. Si vous
SELECT * FROM people WHERE age = 20
, et que vous n'avez pas d'index sur les personnes (âge), cela nécessitera un scan séquentiel O (n) plutôt qu'un scan d'index O (log n) beaucoup plus rapide.Hiérarchie arithmétique entière
Lors de la programmation en C, gardez à l'esprit que certaines opérations arithmétiques sont plus chères que d'autres. Pour les entiers, la hiérarchie se présente comme suit (la moins chère en premier):
+ - ~ & | ^
<< >>
*
/
Certes, le compilateur généralement des choses comme Optimize
n / 2
àn >> 1
automatiquement si vous ciblez un ordinateur grand public, mais si vous ciblez un appareil embarqué, vous pourriez ne pas obtenir ce luxe.Aussi,
% 2
et& 1
ont une sémantique différente. La division et le module arrondissent généralement vers zéro, mais leur implémentation est définie. Bon vieux>>
et&
toujours arrondi vers l'infini négatif, ce qui (à mon avis) a beaucoup plus de sens. Par exemple, sur mon ordinateur:Par conséquent, utilisez ce qui a du sens. Ne pensez pas que vous êtes un bon garçon en utilisant
% 2
quand vous alliez à l'origine écrire& 1
.Opérations en virgule flottante coûteuses
Évitez les opérations lourdes en virgule flottante comme
pow()
etlog()
dans le code qui n'en a pas vraiment besoin, en particulier lorsqu'il s'agit d'entiers. Prenons, par exemple, la lecture d'un nombre:Non seulement cette utilisation de
pow()
(et les conversionsint
<->double
nécessaires pour l'utiliser) est assez chère, mais elle crée une opportunité de perte de précision (incidemment, le code ci-dessus n'a pas de problèmes de précision). C'est pourquoi je grimace quand je vois ce type de fonction utilisé dans un contexte non mathématique.Notez également que l'algorithme "intelligent" ci-dessous, qui se multiplie par 10 à chaque itération, est en fait plus concis que le code ci-dessus:
la source
strlen()
examine la chaîne pointée par son argument pointeur, ce qui signifie qu'elle ne peut pas être const. Aussi,strlen()
est en effet marqué comme pur dans la glibcstring.h
pure
ouconst
et je l'ai même documenté dans le fichier d'en-tête en raison de la différence subtile entre les deux. docs.parrot.org/parrot/1.3.0/html/docs/dev/c_functions.pod.htmlD'après votre question et le fil de commentaires, il semble que vous "pensez" que ce changement de code améliore les performances, mais vous ne savez pas vraiment si c'est le cas ou non.
Je suis fan de la philosophie de Kent Beck :
Ma technique pour améliorer les performances du code, consiste d'abord à faire passer le code aux tests unitaires et à bien le factoriser, puis (en particulier pour les opérations de bouclage) à écrire un test unitaire qui vérifie les performances, puis à refactoriser le code ou à penser à un algorithme différent si celui que je '' ve choisi ne fonctionne pas comme prévu.
Par exemple, pour tester la vitesse avec du code .NET, j'utilise l'attribut Timeout de NUnit pour écrire des affirmations qu'un appel à une méthode particulière s'exécutera dans un certain laps de temps.
En utilisant quelque chose comme l'attribut timeout de NUnit avec l'exemple de code que vous avez donné (et un grand nombre d'itérations pour la boucle), vous pouvez réellement prouver si votre "amélioration" du code a vraiment aidé à la performance de cette boucle.
Un avertissement: Bien que cela soit efficace au niveau "micro", ce n'est certainement pas le seul moyen de tester les performances et ne prend pas en compte les problèmes qui pourraient survenir au niveau "macro" - mais c'est un bon début.
la source
Gardez à l'esprit que votre compilateur peut très bien tourner:
dans:
ou quelque chose de similaire, si
collection
est inchangé sur la boucle.Si ce code se trouve dans une section critique de votre application, il serait utile de savoir si c'est le cas ou non - ou bien si vous pouvez modifier les options du compilateur pour ce faire.
Cela maintiendra la lisibilité du code (car le premier est ce que la plupart des gens s'attendront à voir), tout en vous gagnant ces quelques cycles de machine supplémentaires. Vous êtes alors libre de vous concentrer sur les autres domaines où le compilateur ne peut pas vous aider.
Sur une note latérale: si vous changez
collection
à l'intérieur de la boucle en ajoutant ou en supprimant des éléments (oui, je sais que c'est une mauvaise idée, mais cela arrive), votre deuxième exemple ne bouclera pas sur tous les éléments ou essaiera d'accéder au passé la fin du tableau.la source
Ce type d'optimisation n'est généralement pas recommandé. Ce morceau d'optimisation peut facilement être fait par le compilateur, vous travaillez avec un langage de programmation de niveau supérieur au lieu de l'assemblage, alors pensez au même niveau.
la source
Cela peut ne pas s'appliquer autant au codage à usage général, mais je fais principalement du développement intégré de nos jours. Nous avons un processeur cible spécifique (qui n'allera pas plus vite - il semblera étrangement obsolète au moment où ils retireront le système dans plus de 20 ans), et des délais très restrictifs pour une grande partie du code. Le processeur, comme tous les processeurs, a certaines bizarreries concernant les opérations rapides ou lentes.
Nous avons une technique utilisée pour nous assurer de générer le code le plus efficace, tout en maintenant la lisibilité pour toute l'équipe. Dans les endroits où la construction du langage le plus naturel ne génère pas le code le plus efficace, nous avons créé des macros qui garantissent que le code optimal est utilisé. Si nous faisons un projet de suivi pour un processeur différent, nous pouvons mettre à jour les macros pour la méthode optimale sur ce processeur.
À titre d'exemple spécifique, pour notre processeur actuel, les branches vident le pipeline, bloquant le processeur pendant 8 cycles. Le compilateur prend ce code:
et le transforme en l'équivalent d'assemblage de
Cela prendra soit 3 cycles, soit 10 s'il saute
isReady=1;
. Mais le processeur a unemax
instruction à cycle unique , il est donc préférable d'écrire du code pour générer cette séquence qui est garantie de toujours prendre 3 cycles:De toute évidence, l'intention ici est moins claire que l'original. Nous avons donc créé une macro, que nous utilisons chaque fois que nous voulons une comparaison booléenne supérieure à:
Nous pouvons faire des choses similaires pour d'autres comparaisons. Pour un étranger, le code est un peu moins lisible que si nous n'utilisions que la construction naturelle. Cependant, cela devient rapidement clair après avoir passé un peu de temps à travailler avec le code, et c'est bien mieux que de laisser chaque programmeur expérimenter ses propres techniques d'optimisation.
la source
Eh bien, le premier conseil serait d'éviter de telles optimisations prématurées jusqu'à ce que vous sachiez exactement ce qui se passe avec le code, de sorte que vous soyez sûr de le rendre plus rapide et non plus lent.
En C # par exemple, le compilateur optimisera le code si vous bouclez la longueur d'un tableau, car il sait qu'il n'a pas à vérifier la plage de l'index lorsque vous accédez au tableau. Si vous essayez de l'optimiser en plaçant la longueur du tableau dans une variable, vous romprez la connexion entre la boucle et le tableau et ralentirez en fait le code beaucoup plus.
Si vous allez faire une micro-optimisation, vous devez vous limiter aux choses qui sont connues pour utiliser beaucoup de ressources. S'il n'y a qu'un léger gain de performances, vous devriez plutôt opter pour le code le plus lisible et le plus facile à gérer. Comment le travail informatique change avec le temps, donc quelque chose que vous découvrez est un peu plus rapide maintenant, peut ne pas rester comme ça.
la source
J'ai une technique très simple.
Il y a de nombreuses fois où cela permet d'économiser du temps pour contourner ce processus, mais en général, vous saurez si c'est le cas. En cas de doute, je m'y tiens par défaut.
la source
Profitez du court-circuit:
if(someVar || SomeMethod())
prend autant de temps à coder et est tout aussi lisible que:
if(someMethod() || someVar)
mais cela va être évalué plus rapidement au fil du temps.
la source
Attendez six mois, demandez à votre patron d'acheter de nouveaux ordinateurs à tout le monde. Sérieusement. Le temps du programmeur est beaucoup plus cher que le matériel à long terme. Les ordinateurs hautes performances permettent aux codeurs d'écrire du code de manière simple sans se soucier autant de la vitesse.
la source
Essayez de ne pas trop optimiser à l'avance, puis lorsque vous optimisez, vous vous inquiétez un peu moins de la lisibilité.
Là, je déteste plus que la complexité inutile, mais lorsque vous rencontrez une situation complexe, une solution complexe est souvent requise.
Si vous écrivez le code de la manière la plus évidente, faites un commentaire expliquant pourquoi il a été modifié lorsque vous effectuez le changement complexe.
Cependant, en particulier pour votre sens, je trouve que la plupart du temps, faire l'opposé booléen de l'approche par défaut aide parfois:
peut devenir
Dans de nombreuses langues, tant que vous apportez les ajustements appropriés à la partie "stuff" et qu'elle est toujours lisible. Il n'aborde tout simplement pas le problème de la façon dont la plupart des gens penseraient à le faire en premier car il compte à rebours.
en c # par exemple:
pourrait également s'écrire:
(et oui, vous devriez le faire avec une jointure ou un stringbuilder, mais j'essaie de faire un exemple simple)
Il existe de nombreuses autres astuces que l'on peut utiliser qui ne sont pas difficiles à suivre, mais beaucoup d'entre elles ne s'appliquent pas à toutes les langues, comme l'utilisation du milieu sur le côté gauche d'une affectation dans l'ancien vb pour éviter la pénalité de réaffectation de chaîne ou la lecture de fichiers texte en mode binaire en .net pour dépasser la pénalité de mise en mémoire tampon lorsque le fichier est trop volumineux pour une lecture.
Le seul autre cas vraiment générique auquel je puisse penser qui s'appliquerait partout serait d'appliquer une algèbre booléenne à des conditions complexes pour essayer de transformer l'équation en quelque chose qui a plus de chances de tirer parti d'un court-circuit conditionnel ou de transformer un complexe ensemble d'instructions if-then ou case imbriquées dans une équation entièrement. Ni l'un ni l'autre de ces travaux dans tous les cas, mais ils peuvent être des gains de temps importants.
la source
la source
Utilisez les meilleurs outils que vous pouvez trouver - bon compilateur, bon profileur, bonnes bibliothèques. Obtenez les bons algorithmes, ou mieux encore - utilisez la bonne bibliothèque pour le faire pour vous. Les optimisations de boucle triviales sont de petites pommes de terre, et vous n'êtes pas aussi intelligent que le compilateur d'optimisation.
la source
Le plus simple pour moi est d'utiliser la pile lorsque cela est possible chaque fois qu'un modèle d'utilisation de cas commun correspond à une plage de, par exemple, [0, 64) mais a de rares cas qui n'ont pas de petite limite supérieure.
Exemple simple C (avant):
Et après:
J'ai généralisé cela comme ça depuis que ces types de points chauds surviennent beaucoup dans le profilage:
Ce qui précède utilise la pile lorsque les données allouées sont suffisamment petites dans ces cas à 99,9%, et utilise le tas dans le cas contraire.
En C ++, j'ai généralisé cela avec une petite séquence conforme aux normes (similaire aux
SmallVector
implémentations disponibles) qui tourne autour du même concept.Ce n'est pas une optimisation épique (j'ai obtenu des réductions de, disons, de 3 secondes pour qu'une opération se termine à 1,8 seconde), mais cela nécessite un effort si trivial pour s'appliquer. Lorsque vous pouvez obtenir quelque chose de 3 à 1,8 secondes en introduisant simplement une ligne de code et en en modifiant deux, c'est un très bon coup pour un si petit argent.
la source
Eh bien, il y a beaucoup de changements de performances que vous pouvez effectuer lors de l'accès aux données qui auront un impact énorme sur votre application. Si vous écrivez des requêtes ou utilisez un ORM pour accéder à une base de données, vous devez lire certains livres d'optimisation des performances pour le backend de base de données que vous utilisez. Il y a de fortes chances que vous utilisiez des techniques connues peu performantes. Il n'y a aucune raison de le faire, sauf l'ignorance. Ce n'est pas une optimisation prématurée (je maudis le gars qui a dit cela parce qu'elle a été si largement interprétée que ne jamais se soucier des performances), c'est une bonne conception.
Juste un échantillon rapide des améliorateurs de performances pour SQL Server: utilisez des index appropriés, évitez les curseurs - utilisez une logique basée sur un ensemble, utilisez des clauses sargable where, n'empilez pas les vues au-dessus des vues, ne renvoyez pas plus de données que nécessaire ou plus colonnes que vous avez besoin, n'utilisez pas de sous-requêtes corrélées.
la source
S'il s'agit de C ++, vous devriez prendre l'habitude de
++i
plutôt que dei++
.++i
ne sera jamais pire, cela signifie exactement la même chose qu'une déclaration autonome, et dans certains cas, cela pourrait être une amélioration des performances.Cela ne vaut pas la peine de changer le code existant au cas où cela aiderait, mais c'est une bonne habitude à prendre.
la source
J'ai une vision un peu différente. Le simple fait de suivre les conseils que vous obtenez ici ne fera pas beaucoup de différence, car il y a des erreurs que vous devez faire, que vous devez ensuite corriger, dont vous devez ensuite apprendre.
L'erreur que vous devez faire est de concevoir votre structure de données de la façon dont tout le monde le fait. Autrement dit, avec des données redondantes et de nombreuses couches d'abstraction, avec des propriétés et des notifications qui se propagent à travers la structure en essayant de la garder cohérente.
Ensuite, vous devez effectuer un réglage des performances (profilage) et le faire vous montrer comment, à bien des égards, ce qui vous coûte des tonnes de cycles, ce sont les nombreuses couches d'abstraction, avec des propriétés et des notifications qui se propagent dans toute la structure en essayant de la garder cohérente.
Vous pourrez peut-être résoudre ces problèmes quelque peu sans modifications majeures du code.
Ensuite, si vous avez de la chance, vous pouvez apprendre que moins de structure de données est meilleure et qu'il vaut mieux tolérer une incohérence temporaire que d'essayer de garder bien des choses en accord avec les vagues de messages.
Comment vous écrivez des boucles n'a vraiment rien à voir avec cela.
la source