Il y a déjà beaucoup de questions de performances sur ce site, mais il me semble que presque toutes sont très spécifiques à un problème et assez étroites. Et presque tous répètent le conseil pour éviter une optimisation prématurée.
Assumons:
Ce que je recherche ici, ce sont des stratégies et des astuces pour obtenir les derniers pourcentages dans un algorithme critique quand il ne rest plus qu’à faire quoi que ce soit.
Dans l’idéal, essayez de faire en sorte que les réponses ne soient pas liées à la langue et indiquez les aspects négatifs des stratégies suggérées, le cas échéant.
J’appendai une réponse avec mes propres suggestions initiales et j’attends avec impatience tout ce que la communauté Stack Overflow peut penser.
OK, vous définissez le problème là où il semblerait qu’il n’y ait pas beaucoup de place à l’amélioration. C’est assez rare, selon mon expérience. J’ai essayé d’expliquer cela dans un article du Dr. Dobbs en novembre 1993, en partant d’un programme non sortingvial conventionnel et bien conçu, sans perte évidente, et en le faisant passer par une série d’optimisations jusqu’à ce que son temps soit réduit de 48 heures. secondes à 1,1 seconde et la taille du code source a été réduite d’un facteur de 4. Mon outil de diagnostic était le suivant . La séquence de changements était la suivante:
Le premier problème rencontré était l’utilisation de grappes de listes (maintenant appelées “iterators” et “classes de conteneur”) représentant plus de la moitié du temps. Celles-ci ont été remplacées par un code assez simple, ce qui ramène le temps à 20 secondes.
Maintenant, le plus grand preneur de temps est la création de listes. En pourcentage, ce n’était pas si grand avant, mais maintenant c’est parce que le plus gros problème a été supprimé. Je trouve un moyen de l’accélérer et le temps passe à 17 secondes.
Maintenant, il est plus difficile de trouver des coupables évidents, mais il y en a quelques-uns que je peux faire quelque chose, et le temps passe à 13 secondes.
Maintenant, je semble avoir frappé un mur. Les échantillons me disent exactement ce que ça fait, mais je n’arrive pas à trouver quoi que ce soit que je puisse améliorer. Ensuite, je réfléchis à la conception de base du programme, à sa structure pilotée par les transactions, et je demande si toutes les recherches de liste effectuées sont réellement imposées par les exigences du problème.
Ensuite, je me suis lancé dans une nouvelle conception, où le code du programme est réellement généré (via des macros de préprocesseur) à partir d’un ensemble de sources plus petit, et dans lequel le programme ne détermine pas constamment des choses que le programmeur sait assez prévisibles. En d’autres termes, n’interprétez pas la séquence des choses à faire, comstackz-la.
Maintenant, parce que ça devient tellement rapide, c’est difficile à échantillonner, alors je lui donne 10 fois plus de travail, mais les temps suivants sont basés sur la charge de travail d’origine.
Un diagnostic plus approfondi révèle qu’il passe du temps dans la gestion des files d’attente. Ces modifications réduisent le temps à 7 secondes.
L’impression diagnostique que j’avais faite était devenue un facteur important. Rincez cela – 4 secondes.
Maintenant, les plus grands preneurs de temps sont les appels à malloc et gratuits . Recycler des objects – 2,6 secondes.
En continuant à échantillonner, je trouve toujours des opérations qui ne sont pas ssortingctement nécessaires – 1,1 seconde.
Facteur d’accélération total: 43,6
Maintenant, il n’y a pas deux programmes identiques, mais dans les logiciels non-jouets, j’ai toujours vu une progression comme celle-ci. D’abord, vous obtenez les trucs faciles, et puis le plus difficile, jusqu’à ce que vous atteignez un sharepoint rendement décroissant. La perspicacité que vous gagnez pourrait bien mener à une nouvelle conception, entamant une nouvelle série d’accélérations, jusqu’à ce que vous atteigniez à nouveau des rendements décroissants. Maintenant, c’est le point auquel il pourrait être judicieux de se demander si ++i
ou i++
ou for(;;)
ou while(1)
sont plus rapides: le genre de questions que je vois si souvent sur SO.
PS On peut se demander pourquoi je n’ai pas utilisé de profileur. La réponse est que presque chacun de ces «problèmes» était un site d’appel de fonction, dont les échantillons de stack étaient précis. Les profileurs, même aujourd’hui, viennent à peine de penser que les instructions et les instructions d’appel sont plus importantes à localiser et plus faciles à corriger que les fonctions complètes. J’ai en fait construit un profileur pour faire cela, mais pour une réelle intimité avec ce que le code fait, il n’y a pas de substitut pour bien mettre les doigts dessus. Ce n’est pas un problème que le nombre d’échantillons soit petit, car aucun des problèmes rencontrés n’est si minime qu’il est facile de les rater.
ADDED: jerryjvl a demandé quelques exemples. Voici le premier problème. Il consiste en un petit nombre de lignes de code distinctes, prenant ensemble plus de la moitié du temps:
/* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */ if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){ . . . /* FOR EACH OPERATION REQUEST */ for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){ . . . /* GET CURRENT TASK */ ptask = ILST_NTH(ptop->tasklist, ptop->current_task)
Ils utilisaient le cluster de listes ILST (similaire à une classe de liste). Ils sont mis en œuvre de la manière habituelle, avec “dissimulation d’informations”, ce qui signifie que les utilisateurs de la classe n’étaient pas censés se soucier de la manière dont ils étaient mis en œuvre. Lorsque ces lignes ont été écrites (sur environ 800 lignes de code), on n’a pas pensé à l’idée que ces lignes pourraient constituer un «goulot d’étranglement» (je déteste ce mot). Ils sont simplement la manière recommandée de faire les choses. Il est facile de dire, après coup, que cela aurait dû être évité, mais dans mon expérience, tous les problèmes de performance sont comme ça. En général, il est bon d’essayer d’éviter de créer des problèmes de performance. Il est même préférable de trouver et de corriger ceux qui ont été créés, même s’ils “auraient dû être évités” (avec le recul). J’espère que cela donne un peu de la saveur.
Voici le deuxième problème, en deux lignes distinctes:
/* ADD TASK TO TASK LIST */ ILST_APPEND(ptop->tasklist, ptask) . . . /* ADD TRANSACTION TO TRANSACTION QUEUE */ ILST_APPEND(trnque, ptrn)
Ce sont des listes de construction en ajoutant des éléments à leurs fins. (La solution consistait à collecter les éléments dans des tableaux et à créer les listes en une seule fois.) Ce qui est intéressant, c’est que ces déclarations ne coûtent que 3/48 de l’heure d’origine, c’est-à-dire fait un gros problème au début . Cependant, après avoir éliminé le premier problème, ils ont coûté 3/20 du temps et sont maintenant devenus des “poissons plus gros”. En général, c’est comme ça que ça se passe.
J’appendai que ce projet a été réalisé à partir d’un projet réel sur lequel j’ai aidé. Dans ce projet, les problèmes de performance étaient beaucoup plus dramatiques (tout comme les accélérations), comme l’appel d’une routine d’access à la firebase database dans une boucle interne pour voir si une tâche était terminée.
Référence ajoutée: Le code source, original et repensé, se trouve sur http://www.ddj.com , pour 1993, dans le fichier 9311.zip, les fichiers slug.asc et slug.zip.
EDIT 2011/11/26: Il existe maintenant un projet sourceforge contenant le code source dans Visual C ++ et une description détaillée de la façon dont il a été réglé. Il ne traverse que la première partie du scénario décrit ci-dessus, et ne suit pas exactement la même séquence, mais obtient tout de même une accélération de l’ordre de 2 à 3.
Suggestions:
Si vous ne pouvez plus améliorer les performances, voyez si vous pouvez améliorer les performances perçues .
Vous ne pourrez peut-être pas rendre votre algorithme fooCalc plus rapide, mais il existe souvent des moyens pour que votre application semble plus sensible à l’utilisateur.
Quelques exemples:
Cela ne rendra pas votre programme plus rapide, mais cela rendra vos utilisateurs plus heureux avec la vitesse dont vous disposez.
Je passe la majeure partie de ma vie à cet endroit. Les grandes lignes permettent d’exécuter votre profileur et de le faire enregistrer:
__ressortingct
libéralement pour promettre le compilateur à propos de l’aliasing. Et encore une chose que j’aime faire:
Jetez plus de matériel dessus!
Plus de suggestions:
Évitez les E / S : toute E / S (disque, réseau, ports, etc.) sera toujours beaucoup plus lente que tout code effectuant des calculs. Supprimez donc les E / S dont vous n’avez pas absolument besoin.
Déplacer les E / S en amont : Chargez toutes les données dont vous avez besoin pour un calcul initial afin de ne pas avoir à attendre des E / S répétitives dans le cœur d’un algorithme critique (et peut-être en conséquence de répéter disque recherche, lors du chargement de toutes les données en un seul coup peut éviter de chercher).
Retarder les E / S : N’écrivez pas vos résultats tant que le calcul n’est pas terminé, stockez-les dans une structure de données, puis videz-les en une fois à la fin du travail.
Filetage I / O : Pour ceux qui ont le courage de combiner le ‘I / O up-front’ ou ‘Delay I / O’ avec le calcul réel en déplaçant le chargement dans un thread parallèle, vous pouvez travailler pendant que vous chargez plus de données sur un calcul sur les données que vous avez déjà, ou pendant que vous calculez le lot de données suivant, vous pouvez écrire simultanément les résultats du dernier lot.
Étant donné que la plupart des problèmes de performances impliquent des problèmes de firebase database, je vais vous donner quelques informations spécifiques à prendre en compte lors du réglage des requêtes et des procédures stockées.
Évitez les curseurs dans la plupart des bases de données. Évitez de boucler aussi. La plupart du temps, l’access aux données doit être basé sur les ensembles et non sur l’enregistrement. Cela inclut de ne pas réutiliser une seule procédure stockée d’enregistrement lorsque vous souhaitez insérer 1 000 000 d’enregistrements à la fois.
N’utilisez jamais select *, ne renvoyez que les champs dont vous avez réellement besoin. Cela est particulièrement vrai s’il y a des jointures car les champs de jointure seront répétés et provoqueront ainsi une charge inutile sur le serveur et sur le réseau.
Évitez d’utiliser des sous-requêtes corrélées. Utilisez des jointures (y compris des jointures à des tables dérivées lorsque cela est possible) (je sais que cela est vrai pour Microsoft SQL Server, mais testez les conseils lorsque vous utilisez un backend différent).
Index, index, index. Et obtenez ces statistiques mises à jour si applicable à votre firebase database.
Rendez la requête sargable . Cela signifie éviter les choses qui rendent impossible l’utilisation des index, comme l’utilisation d’un caractère générique dans le premier caractère d’une clause like ou d’une fonction dans la jointure ou la partie gauche d’une instruction where.
Utilisez des types de données corrects. Il est plus rapide d’effectuer des calculs de date sur un champ de date que d’essayer de convertir un type de données chaîne en un type de données date, puis d’effectuer le calcul.
Ne jamais mettre une boucle d’aucune sorte dans un déclencheur!
La plupart des bases de données ont un moyen de vérifier comment l’exécution de la requête sera effectuée. Dans Microsoft SQL Server, cela s’appelle un plan d’exécution. Cochez les premiers pour voir où se trouvent les zones problématiques.
Examinez la fréquence d’exécution de la requête ainsi que le temps d’exécution nécessaire pour déterminer ce qui doit être optimisé. Parfois, vous pouvez obtenir plus de performances, d’un simple ajustement à une requête qui s’exécute des millions de fois par jour, qu’en effaçant une requête long_running qui ne s’exécute qu’une fois par mois.
Utilisez un outil de profileur pour savoir ce qui est réellement envoyé vers et depuis la firebase database. Je peux me rappeler une fois dans le passé où nous ne pouvions pas comprendre pourquoi la page était si lente à charger lorsque la procédure stockée était rapide et à travers le profilage que la page Web demandait la requête plusieurs fois au lieu d’une fois.
Le profileur vous aidera également à trouver qui bloque qui. Certaines requêtes qui s’exécutent rapidement lors de l’exécution seule peuvent devenir très lentes en raison de verrous provenant d’autres requêtes.
Le facteur limitant le plus important aujourd’hui est la bande passante limitée . Les multicores ne font qu’empirer les choses, car la bande passante est partagée entre les cœurs. En outre, la zone de puce limitée consacrée à la mise en œuvre des caches est également divisée entre les cœurs et les threads, aggravant encore ce problème. Enfin, la signalisation inter-puces nécessaire à la cohérence des différents caches augmente également avec le nombre croissant de cœurs. Cela ajoute également une pénalité.
Ce sont les effets que vous devez gérer. Parfois, par micro-gestion de votre code, mais parfois par une reflection et une refactorisation minutieuses.
Beaucoup de commentaires mentionnent déjà le code convivial du cache. Il y a au moins deux saveurs distinctes de ceci:
Le premier problème concerne spécifiquement la régularité de vos modèles d’access aux données, ce qui permet au pré-parsingur matériel de fonctionner efficacement. Évitez l’allocation dynamic de mémoire qui répartit vos objects de données en mémoire. Utilisez des conteneurs linéaires au lieu de listes liées, de hachages et d’arbres.
Le deuxième problème concerne l’amélioration de la réutilisation des données. Modifiez vos algorithmes pour travailler sur des sous-ensembles de vos données qui correspondent au cache disponible, et réutilisez ces données autant que possible pendant qu’il est encore dans le cache.
Le regroupement des données et le fait que vous utilisez toutes les données contenues dans les lignes de cache dans les boucles dynamics vous aideront à éviter ces autres effets et à adapter davantage de données utiles dans le cache.
Vous devriez probablement considérer la “perspective Google”, c’est-à-dire déterminer comment votre application peut être largement parallélisée et concurrente, ce qui impliquera inévitablement de répartir votre application sur différents ordinateurs et réseaux, de façon avec le matériel que vous lui lancez.
D’autre part, les employés de Google sont également connus pour avoir déployé beaucoup de main-d’œuvre et de ressources pour résoudre certains problèmes liés aux projets, outils et infrastructures qu’ils utilisent, comme par exemple l’optimisation de programmes complets pour gcc. piratage des équipements internes gcc afin de le préparer aux scénarios de cas d’utilisation typiques de Google.
De même, profiler une application ne signifie plus simplement profiler le code du programme, mais également tous ses systèmes et infrastructures (réseaux, commutateurs, serveurs, grappes RAID) afin d’identifier les redondances et le potentiel d’optimisation du sharepoint vue du système.
Bien que j’apprécie la réponse de Mike Dunlavey, en fait, c’est une excellente réponse avec un exemple à l’appui, je pense que cela pourrait s’exprimer très simplement:
Découvrez d’abord ce qui prend le plus de temps et comprenez pourquoi.
C’est le processus d’identification des cochons de temps qui vous aide à comprendre où vous devez affiner votre algorithme. C’est la seule réponse indépendante du langage que je puisse trouver pour un problème qui doit déjà être optimisé. En supposant que vous voulez être indépendant de l’architecture dans votre quête de vitesse.
Donc, même si l’algorithme peut être optimisé, sa mise en œuvre peut ne pas l’être. L’identification vous permet de savoir quelle est la partie: algorithme ou implémentation. Donc, quel que soit le moment le plus opportun, votre candidat principal à la révision. Mais comme vous dites que vous voulez réduire les derniers pourcentages, vous voudrez peut-être aussi examiner les parties moins importantes, les parties que vous n’avez pas examinées de près au début.
Enfin, un peu d’essais et d’erreurs avec les performances de différentes manières d’implémenter la même solution, ou des algorithmes potentiellement différents, peut apporter des informations qui aident à identifier les pertes de temps et les gains de temps.
HPH, asoudmove.
Diviser et conquérir
Si le jeu de données en cours de traitement est trop volumineux, parcourez-le en morceaux. Si vous avez bien fait votre code, l’implémentation devrait être facile. Si vous avez un programme monolithique, maintenant vous savez mieux.
Tout d’abord, comme mentionné dans plusieurs réponses précédentes, apprenez ce qui mord votre performance: mémoire, processeur, réseau, firebase database ou autre chose. Selon cela …
… si c’est la mémoire – trouvez l’un des livres écrits il y a longtemps par Knuth, l’un des “The Art of Computer Programming”. Il est fort probable que ce soit une question de sorting et de recherche – si ma mémoire est erronée, vous devrez découvrir comment gérer le stockage lent des données sur bande. Transformez mentalement sa mémoire / paire de bandes dans votre paire de cache / mémoire principale (ou dans une paire de cache L1 / L2) respectivement. Étudiez toutes les astuces qu’il décrit – si vous ne trouvez pas quelque chose qui résout votre problème, engagez un informaticien professionnel pour mener une recherche professionnelle. Si votre problème de mémoire est dû au hasard avec les FFT (cache des index avec index inversés lorsque vous faites des papillons radix-2), n’embauchez pas de scientifique. Optimisez manuellement les passes une à une jusqu’à ce que vous obteniez ou gagnez à impasse. Vous avez mentionné la suppression jusqu’à quelques pour cent, n’est-ce pas? Si c’est peu, vous gagnerez probablement.
… si c’est processeur – passez en langage d’assemblage. Etude de la spécification du processeur: qu’est – ce qui compte , VLIW, SIMD. Les appels de fonction sont très probablement des mangeurs de tiques remplaçables. Apprendre les transformations de la boucle – pipeline, dérouler. Les multiplications et les divisions peuvent être remplaçables / interpolées avec des décalages de bits (les multiplications par petits entiers peuvent être remplaçables par des ajouts). Essayez des astuces avec des données plus courtes – si vous êtes chanceux, une instruction avec 64 bits peut s’avérer remplaçable avec deux sur 32 ou même 4 sur 16 ou 8 sur 8 bits. Essayez également des données plus longues – par exemple, vos calculs de flottement peuvent se révéler plus lents que les calculs doubles sur un processeur donné. Si vous avez des éléments sortinggonomésortingques, combattez-les avec des tables pré-calculées; N’oubliez pas que le sinus de petite valeur peut être remplacé par cette valeur si la perte de précision se situe dans les limites autorisées.
… si c’est un réseau – pensez à compresser les données que vous passez dessus. Remplacez le transfert XML par des binarys. Protocoles d’étude Essayez UDP au lieu de TCP si vous pouvez gérer en quelque sorte la perte de données.
… si c’est la firebase database, eh bien, allez dans n’importe quel forum de firebase database et demandez conseil. Grille de données en mémoire, optimisation du plan de requête, etc., etc.
HTH 🙂
Je pense que cela a déjà été dit différemment. Mais lorsque vous utilisez un algorithme utilisant beaucoup de processeurs, vous devez tout simplifier dans la boucle la plus interne au désortingment de tout le rest.
Cela peut sembler évident pour certains, mais c’est quelque chose sur lequel j’essaie de me concentrer indépendamment de la langue avec laquelle je travaille. Si vous traitez par exemple des boucles nestedes et que vous trouvez une opportunité de prendre du code à un niveau, vous pouvez dans certains cas accélérer considérablement votre code. Comme autre exemple, il y a des petites choses à considérer, comme travailler avec des entiers au lieu de variables à virgule flottante quand vous le pouvez, et utiliser la multiplication plutôt que la division chaque fois que vous le pouvez. Encore une fois, ce sont des choses à considérer pour votre boucle la plus intérieure.
Parfois, vous pouvez trouver utile d’effectuer vos opérations mathématiques sur un nombre entier à l’intérieur de la boucle interne, puis de le réduire à une variable à virgule flottante avec laquelle vous pourrez travailler par la suite. C’est un exemple de sacrifier la vitesse dans une section pour améliorer la vitesse dans une autre, mais dans certains cas, le gain peut en valoir la peine.
Caching! Un moyen peu coûteux (dans les efforts du programmeur) de faire presque n’importe quoi plus rapidement consiste à append une couche d’abstraction de mise en cache à n’importe quelle zone de mouvement de données de votre programme. Qu’il s’agisse d’E / S ou simplement de passage / création d’objects ou de structures. Il est souvent facile d’append des caches aux classes d’usine et aux lecteurs / rédacteurs.
Parfois, le cache ne vous rapportera pas grand chose, mais c’est une méthode simple pour append la mise en cache partout, puis la désactiver là où cela ne vous aide pas. I’ve often found this to gain huge performance without having to micro-parsing the code.
Very difficult to give a generic answer to this question. It really depends on your problem domain and technical implementation. A general technique that is fairly language neutral: Identify code hotspots that cannot be eliminated, and hand-optimize assembler code.
Last few % is a very CPU and application dependent thing….
The list goes on…. But these sorts of things really are the last resort…
Build for x86, and run Valgrind /Cachegrind against the code for proper performance profiling. Or Texas Instruments’ CCStudio has a sweet profiler. Then you’ll really know where to focus…
Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?
For any non-offline projects, while having best software and best hardware, if your throughoutput is weak, then that thin line is going to squeeze data and give you delays, albeit in milliseconds… but if you are talking about the last drops, that’s a some drops gained, 24/7 for any packge sent or received.
I’ve spent some time working on optimising client/server business systems operating over low-bandwidth and long-latency networks (eg satellite, remote, offshore), and been able to achieve some dramatic performance improvements with a fairly repeatable process.
Measure : Start by understanding the network’s underlying capacity and topology. Talking to the relevant networking people in the business, and make use of basic tools such as ping and traceroute to establish (at a minimum) the network latency from each client location, during typical operational periods. Next, take accurate time measurements of specific end user functions that display the problematic symptoms. Record all of these measurements, along with their locations, dates and times. Consider building end-user “network performance testing” functionality into your client application, allowing your power users to participate in the process of improvement; empowering them like this can have a huge psychological impact when you’re dealing with users frustrated by a poorly performing system.
Analyze : Using any and all logging methods available to establish exactly what data is being transmitted and received during the execution of the affected operations. Ideally, your application can capture data transmitted and received by both the client and the server. If these include timestamps as well, even better. If sufficient logging isn’t available (eg closed system, or inability to deploy modifications into a production environment), use a network sniffer and make sure you really understand what’s going on at the network level.
Cache : Look for cases where static or infrequently changed data is being transmitted repetitively and consider an appropriate caching strategy. Typical examples include “pick list” values or other “reference entities”, which can be surprisingly large in some business applications. In many cases, users can accept that they must restart or refresh the application to update infrequently updated data, especially if it can shave significant time from the display of commonly used user interface elements. Make sure you understand the real behaviour of the caching elements already deployed – many common caching methods (eg HTTP ETag) still require a network round-sortingp to ensure consistency, and where network latency is expensive, you may be able to avoid it altogether with a different caching approach.
Parallelise : Look for sequential transactions that don’t logically need to be issued ssortingctly sequentially, and rework the system to issue them in parallel. I dealt with one case where an end-to-end request had an inherent network delay of ~2s, which was not a problem for a single transaction, but when 6 sequential 2s round sortingps were required before the user regained control of the client application, it became a huge source of frustration. Discovering that these transactions were in fact independent allowed them to be executed in parallel, reducing the end-user delay to very close to the cost of a single round sortingp.
Combine : Where sequential requests must be executed sequentially, look for opportunities to combine them into a single more comprehensive request. Typical examples include creation of new entities, followed by requests to relate those entities to other existing entities.
Compress : Look for opportunities to leverage compression of the payload, either by replacing a textual form with a binary one, or using actual compression technology. Many modern (ie within a decade) technology stacks support this almost transparently, so make sure it’s configured. I have often been surprised by the significant impact of compression where it seemed clear that the problem was fundamentally latency rather than bandwidth, discovering after the fact that it allowed the transaction to fit within a single packet or otherwise avoid packet loss and therefore have an outsize impact on performance.
Repeat : Go back to the beginning and re-measure your operations (at the same locations and times) with the improvements in place, record and report your results. As with all optimisation, some problems may have been solved exposing others that now dominate.
In the steps above, I focus on the application related optimisation process, but of course you must ensure the underlying network itself is configured in the most efficient manner to support your application too. Engage the networking specialists in the business and determine if they’re able to apply capacity improvements, QoS, network compression, or other techniques to address the problem. Usually, they will not understand your application’s needs, so it’s important that you’re equipped (after the Analyse step) to discuss it with them, and also to make the business case for any costs you’re going to be asking them to incur. I’ve encountered cases where erroneous network configuration caused the applications data to be transmitted over a slow satellite link rather than an overland link, simply because it was using a TCP port that was not “well known” by the networking specialists; obviously rectifying a problem like this can have a dramatic impact on performance, with no software code or configuration changes necessary at all.
Not nearly as in depth or complex as previous answers, but here goes: (these are more beginner/intermediate level)
Impossible to say. It depends on what the code looks like. If we can assume that the code already exists, then we can simply look at it and figure out from that, how to optimize it.
Better cache locality, loop unrolling, Try to eliminate long dependency chains, to get better instruction-level parallelism. Prefer conditional moves over twigs when possible. Exploit SIMD instructions when possible.
Understand what your code is doing, and understand the hardware it’s running on. Then it becomes fairly simple to determine what you need to do to improve performance of your code. That’s really the only truly general piece of advice I can think of.
Well, that, and “Show the code on SO and ask for optimization advice for that specific piece of code”.
If better hardware is an option then definitely go for that. Autrement
The google way is one option “Cache it.. Whenever possible don’t touch the disk”
Here are some quick and dirty optimization techniques I use. I consider this to be a ‘first pass’ optimization.
Learn where the time is spent Find out exactly what is taking the time. Is it file IO? Is it CPU time? Is it the network? Is it the Database? It’s useless to optimize for IO if that’s not the bottleneck.
Know Your Environment Knowing where to optimize typically depends on the development environment. In VB6, for example, passing by reference is slower than passing by value, but in C and C++, by reference is vastly faster. In C, it is reasonable to try something and do something different if a return code indicates a failure, while in Dot Net, catching exceptions are much slower than checking for a valid condition before attempting.
Indexes Build indexes on frequently queried database fields. You can almost always trade space for speed.
Avoid lookups Inside of the loop to be optimized, I avoid having to do any lookups. Find the offset and/or index outside of the loop and reuse the data inside.
Minimize IO try to design in a manner that reduces the number of times you have to read or write especially over a networked connection
Reduce Abstractions The more layers of abstraction the code has to work through, the slower it is. Inside the critical loop, reduce abstractions (eg reveal lower-level methods that avoid extra code)
Spawn Threads for projects with a user interface, spawning a new thread to preform slower tasks makes the application feel more responsive, although isn’t.
Pre-process You can generally trade space for speed. If there are calculations or other intense operations, see if you can precompute some of the information before you’re in the critical loop.
Sometimes changing the layout of your data can help. In C, you might switch from an array or structures to a structure of arrays, or vice versa.
Tweak the OS and framework.
It may sound an overkill but think about it like this: Operating Systems and Frameworks are designed to do many things. Your application only does very specific things. If you could get the OS do to exactly what your application needs and have your application understand how the the framework (php,.net,java) works, you could get much better out of your hardware.
Facebook, for example, changed some kernel level thingys in Linux, changed how memcached works (for example they wrote a memcached proxy, and used udp instead of tcp ).
Another example for this is Window2008. Win2K8 has a version were you can install just the basic OS needed to run X applicaions (eg Web-Apps, Server Apps). This reduces much of the overhead that the OS have on running processes and gives you better performance.
Of course, you should always throw in more hardware as the first step…
pass by reference instead of by value
Reduce variable sizes (in embedded systems)
If your variable size is larger than the word size on a specific architecture, it can have a significant effect on both code size and speed. For example, if you have a 16 bit system, and use a long int
variable very often, and later realize that it can never get outside the range (−32.768 … 32.767) consider reducing it to short int.
From my personal experience, if a program is ready or almost ready, but we realize it takes up about 110% or 120% of the target hardware’s program memory, a quick normalization of variables usually solves the problem more often than not.
By this time, optimizing the algorithms or parts of the code itself can become frustratingly futile:
Many people make the mistake of having variables which exactly store the numerical value of a unit they use the variable for: for example, their variable time
stores the exact number of milliseconds, even if only time steps of say 50 ms are relevant. Maybe if your variable represented 50 ms for each increment of one, you would be able to fit into a variable smaller or equal to the word size. On an 8 bit system, for example, even a simple addition of two 32-bit variables generates a fair amount of code, especially if you are low on registers, while 8 bit additions are both small and fast.