it-swarm-fr.com

Quelles techniques simples utilisez-vous pour améliorer les performances?

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 typique que nous avons appris:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

Mais, je le fais habituellement 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 appellera la méthode length une fois que ... ma petite amie dit que c'est cryptique cependant. Y a-t-il une autre chose simple que vous utilisez sur vos propres développements?

21
Cristian

De votre question et du thread de commentaire, cela ressemble à vous "penser" que ce code change d'amélioration de la performance, mais vous ne savez pas vraiment si cela le fait ou non.

Je suis un fan de Kent Beck's Philosophie:

"Faites-le travailler, faites-le bien, faites-le vite."

Ma technique pour améliorer les performances du code, est d'abord obtenir le code qui passe les tests de l'unité et bien pris en charge, puis (en particulier (en particulier les opérations de boucle), écrivez un test de l'unité qui vérifie les performances, puis refactorise le code ou penser à un autre algorithme si celui que je ' VE choisi ne fonctionne pas comme prévu.

Par exemple, pour tester la vitesse avec le code .NET, j'utilise attribut timeout de Nunit Pour écrire des affirmations selon lesquelles un appel à une méthode particulière sera exécuté dans un certain 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 prouver que votre "amélioration" au code ait vraiment aidé avec la performance de cette boucle.

Un de non-responsabilité: 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 pouvant survenir au niveau "macro" - mais c'est un bon départ.

13
Paddyslacker

Gardez à l'esprit que votre compilateur peut bien tourner:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

dans:

int j = collection.length();
for(int i = 0; i < j; i++ ){
   // stuff here
}

ou quelque chose de similaire, si collection est inchangé sur la boucle.

Si ce code est dans une section critique de votre demande, il serait utile de déterminer si c'est le cas ou non - ou même si vous pouvez modifier les options du compilateur pour le faire.

Cela maintiendra la lisibilité du code (comme le premier est ce que la plupart des gens s'attendent à voir), tout en vous gagnant ces quelques cycles de machine supplémentaires. Vous êtes alors libre de vous concentrer sur les autres zones 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 se produit) alors votre deuxième exemple ne boucle pas sur tous les éléments ou essayera d'accéder au-delà de la fin de la matrice.

11
ChrisF

Ce type d'optimisation n'est généralement pas recommandé. Cet élément d'optimisation peut facilement être effectué par compilateur, vous travaillez avec une langue de programmation de niveau supérieur au lieu d'assemblage, alors pensez au même niveau.

9
tactoth

Eh bien, le premier conseil serait d'éviter d'autres optimisations prématurées jusqu'à ce que vous sachiez exactement ce qui se passe au code, afin que vous soyez sûr que vous rendant réellement plus rapidement et que vous n'êtes pas plus lent.

En C # Par exemple, le compilateur optimisera le code si vous bouclez la longueur d'un tableau, car elle sait qu'elle ne doit pas nécessiter de vérifier l'index lorsque vous accédez à la matrice. Si vous essayez de l'optimiser en mettant la longueur de la matrice dans une variable, vous allumerez la connexion entre la boucle et la matrice, et rendrez le code beaucoup plus lentement.

Si vous allez micro-optimiser, vous devez vous limiter à des choses connues pour utiliser beaucoup de ressources. S'il n'y a qu'un léger gain de performance, vous devriez aller avec le code le plus lisible et le plus maintenu. Comment le travail informatique change au fil du temps, alors quelque chose que vous trouvez est légèrement plus rapide maintenant, peut ne pas rester de cette façon.

3
Guffa

J'ai une technique très simple.

  1. Je fais mon travail de code.
  2. Je le teste à la vitesse.
  3. Si c'est rapide, je retourne à l'étape 1 pour une autre caractéristique. Si c'est lent, je profile à trouver le goulot d'étranglement.
  4. Je fixe le goulot d'étranglement. Retournez à l'étape 1.

Il y a beaucoup de fois où il économise du temps pour contourner ce processus, mais en général, vous saurez si c'est le cas. S'il y a un doute, je m'en tiens par défaut.

3
Jason Baker

Profitez du court-circuit :

if(someVar || SomeMethod())

prend aussi longtemps que le code et est juste aussi lisible que:

if(someMethod() || someVar)

pourtant, cela va évaluer plus rapidement au fil du temps.

2
realworldcoder
  1. Profil. Avons-nous même un problème? Où?
  2. Dans 90% de cas où il est en quelque sorte IO lié, appliquez la mise en cache (et peut-être obtenir plus de mémoire)
  3. Si c'est CPU lié, appliquez la mise en cache
  4. Si la performance est toujours un problème, nous avons quitté le domaine des techniques simples - faites les mathématiques.
1
Maglob

Le plus simple pour moi utilise la pile si possible chaque fois qu'un motif d'utilisation de cas commun convient à une gamme de, disons, [0, 64) mais a de rares cas qui n'ont pas de petite limite supérieure.

Simple C exemple (avant):

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int* values = calloc(n, sizeof(int));

    // do stuff with values
    ...
    free(values);
}

Et après:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int values_mem[64] = {0}
    int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int));

    // do stuff with values
    ...
    if (values != values_mem)
        free(values);
}

J'ai généralisé ceci comme si ces types d'hotspots cultivent beaucoup dans le profilage:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    MemFast values_mem;
    int* values = mf_calloc(&values_mem, n, sizeof(int));

    // do stuff with values
    ...

    mf_free(&values_mem);
}

Ce qui précède utilise la pile lorsque les données étant attribuées sont suffisamment petites dans ces cas de 99,9% et utilisent le tas autrement.

En C++, j'ai généralisé cela avec une petite séquence compatible standard (similaire à SmallVector mises en œuvre) qui tournent autour du même concept.

Ce n'est pas une optimisation épique (j'ai obtenu des réductions de, disons, 3 secondes pour une opération complète à 1,8 seconde), mais elle nécessite un tel effort trivial pour appliquer. Lorsque vous pouvez obtenir quelque chose de moins de 3 secondes à 1,8 seconde en introduisant une ligne de code et en changeant deux, c'est une très bonne baisse pour un tel argent.

1
user204677

tilisez les meilleurs outils que vous pouvez trouver - Bon compilateur, bon profileur, bonnes bibliothèques. Obtenez les algorithmes correctement, ou mieux encore - utilisez la bonne bibliothèque pour le faire pour vous. Les optimisations de boucle triviale sont de petites pommes de terre, plus vous n'êtes pas aussi intelligemment que le compilateur d'optimisation.

1
Job

Attendez six mois, faites que votre patron achète à tous les nouveaux ordinateurs. Sérieusement. Le temps de programmeur est beaucoup plus cher que le matériel à long terme. Les ordinateurs haute performance permettent aux codeurs d'écrire du code de manière simple sans être aussi préoccupé par la vitesse.

1
Michael Kristofik

Essayez de ne pas trop optimiser l'avance à l'avance, alors lorsque vous optimisez l'inquiétude un peu moins sur la lisibilité.

Là peu je déteste plus qu'une complexité inutile, mais lorsque vous avez frappé une situation complexe, une solution complexe est souvent nécessaire.

Si vous écrivez le code la manière la plus évidente, faites ensuite un commentaire expliquant pourquoi il a été modifié lorsque vous effectuez le changement complexe.

Plus précisément à votre sens cependant, je trouve que beaucoup d'efforts que font le contraire booléen de l'approche par défaut contribue parfois:

for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}

peut devenir

for(int i = collection.length(); i > 0; i-=1 ){
// stuff here
}

Dans de nombreuses langues tant que vous apportez des ajustements appropriés à la partie "Stuff" et qu'il est toujours lisible. Cela n'approche tout simplement pas le problème de la façon dont la plupart des gens penseraient à le faire en premier parce qu'il compte en arrière.

en C # Par exemple:

        string[] collection = {"a","b"};

        string result = "";

        for (int i = 0, j = collection.Count() - 1; i < j; i++)
        {
            result += collection[i] + "~";
        }

pourrait aussi être écrit comme:

        for (int i = collection.Count() - 1; i > 0; i -= 1)
        {
            result = collection[i] + "~" + result;
        }

(Et oui, vous devriez que vous soyez avec une jointure ou un stringbuilder, mais j'essaie de faire un exemple simple)

Il y a beaucoup d'autres astuces que l'on peut utiliser qui ne sont pas difficiles à suivre, mais beaucoup d'entre eux ne s'appliquent pas dans toutes les langues telles que l'utilisation de la mi-course d'une affectation dans Old VB afin d'é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é tampon lorsque le fichier est trop gros pour un Readtoend.

Le seul autre cas vraiment générique que je puisse penser qui s'appliquerait partout, il s'agirait d'appliquer une algèbre booléenne à des conditionnels complexes pour tenter de transformer l'équation à quelque chose de mieux tirer parti d'un conditionnel à court-circuiter ou de transformer un complexe ensemble de désignés si, puis ou de cas de cas dans une équation entièrement. Ni l'un ni l'autre de ces travaux dans tous les cas, mais ils peuvent être des économiseurs de temps importants.

1
Bill

J'ai un peu différent. Simplement simplement les conseils que vous obtenez ici ne va pas faire beaucoup de différence, car il y a des erreurs que vous devez faire, que vous devez ensuite corriger, que vous devez ensuite apprendre de.

L'erreur que vous devez faire est de concevoir votre structure de données de la manière dont tout le monde le fait. C'est-à-dire que les données redondantes et de nombreuses couches d'abstraction, avec des propriétés et des notifications propagent tout au long de la structure qui tentent de le garder cohérent.

Ensuite, vous besoin de syntonisation de performance (Profilage) et de vous montrer comment, à bien des égards, ce qui vous coûte que vous coûtez des oodles de cycles est les nombreuses couches d'abstraction, avec des propriétés et des notifications propagent dans tout le monde. structure en essayant de le garder cohérent.

Vous pourrez peut-être résoudre ces problèmes quelque peu de problèmes sans modifications majeures au code.

Ensuite, si vous avez de la chance, vous pouvez apprendre que moins de structure de données est meilleure, et qu'il est préférable de tolérer une incohérence temporaire que d'essayer de garder de nombreuses choses étroitement en accord avec des vagues de messages.

Comment écrivez-vous des boucles n'a vraiment rien à voir avec ça.

0
Mike Dunlavey

Eh bien, il y a beaucoup de changements de performance que vous pouvez apporter lors de l'accès à des données qui auront un impact énorme sur votre application. Si vous écrivez des requêtes ou utilisez un oram pour accéder à une base de données, vous devez lire des livres de réglage de la performance pour le backend de la base de données que vous utilisez. Les chances sont que vous utilisez des techniques connues mal performantes. Il n'y a aucune raison de faire cela sauf l'ignorance. Ce n'est pas une optimisation prématurée (je maudis le gars qui l'a dit parce que cela a été aussi largement interprété comme ne vous inquiétant jamais pour la performance), c'est un bon design.

Juste un échantillon rapide d'ensemencieurs de performance pour SQL Server: Utilisez des index appropriés, évitez les curseurs - Utilisez une logique à base de jeu, utilisez Sargable où des clauses, ne piquent pas de vues sur des vues, ne retournez pas plus de données que nécessaire ou plus Les colonnes que vous n'avez besoin, n'utilisez pas de sous-requêtes corrélées.

0
HLGEM

S'il s'agit de C++, vous devriez avoir l'habitude de ++i plutôt que i++. ++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 de la performance.

Il ne vaut pas la peine d'être modifié le code existant sur la possibilité d'aider, mais c'est une bonne habitude d'entrer.

0
David Thornley