it-swarm.com.de

Leistungsoptimierungsstrategien der letzten Instanz

Auf dieser Site gibt es bereits viele Leistungsfragen, aber mir fällt auf, dass fast alle sehr problemspezifisch und ziemlich eng sind. Und fast alle wiederholen den Rat, um vorzeitige Optimierung zu vermeiden.

Angenommen:

  • der Code funktioniert bereits richtig
  • die gewählten Algorithmen sind bereits für die Umstände des Problems optimal
  • der Code wurde gemessen und die fehlerhaften Routinen wurden isoliert
  • alle Optimierungsversuche werden auch gemessen, um sicherzustellen, dass sie die Sache nicht verschlimmern

Was ich hier suche, sind Strategien und Tricks, mit denen ich in einem kritischen Algorithmus die letzten paar Prozent herausholen kann, wenn nur noch das Nötigste zu tun ist.

Versuchen Sie im Idealfall, die Antworten sprachunabhängig zu machen, und weisen Sie gegebenenfalls auf die Nachteile der vorgeschlagenen Strategien hin.

Ich werde eine Antwort mit meinen ersten Vorschlägen hinzufügen und mich auf alles freuen, was die Stack Overflow-Community sonst noch denken kann.

595
jerryjvl

OK, Sie definieren das Problem dahingehend, wo es nicht viel Raum für Verbesserungen zu geben scheint. Das ist meiner Erfahrung nach ziemlich selten. Ich versuchte dies in einem Artikel von Dr. Dobbs im November 1993 zu erklären, indem ich von einem konventionell gut konzipierten, nicht trivialen Programm ohne offensichtliche Verschwendung ausging und es durch eine Reihe von Optimierungen führte, bis seine Wanduhrzeit von 48 Sekunden verkürzt wurde auf 1,1 Sekunden und die Größe des Quellcodes wurde um den Faktor 4 reduziert. Mein Diagnosetool war dies . Die Reihenfolge der Änderungen war wie folgt:

  • Das erste gefundene Problem war die Verwendung von Listenclustern (jetzt als "Iteratoren" und "Containerklassen" bezeichnet), die mehr als die Hälfte der Zeit ausmachen. Diese wurden durch relativ einfachen Code ersetzt, wodurch die Zeit auf 20 Sekunden verkürzt wurde.

  • Jetzt ist der größte Zeitnehmer das Erstellen von Listen. In Prozent war es früher nicht so groß, aber jetzt liegt es daran, dass das größere Problem beseitigt wurde. Ich finde einen Weg, um es zu beschleunigen, und die Zeit sinkt auf 17 Sekunden.

  • Jetzt ist es schwieriger, offensichtliche Schuldige zu finden, aber es gibt ein paar kleinere, gegen die ich etwas unternehmen kann, und die Zeit sinkt auf 13 Sekunden.

Jetzt bin ich an eine Wand gestoßen. Die Beispiele sagen mir genau, was es tut, aber ich kann anscheinend nichts finden, was ich verbessern kann. Dann denke ich über das grundlegende Design des Programms nach, über seine transaktionsgesteuerte Struktur und frage, ob die gesamte Listensuche, die es durchführt, tatsächlich von den Anforderungen des Problems abhängig ist.

Dann bin ich auf ein Re-Design gestoßen, bei dem der Programmcode tatsächlich (über Präprozessor-Makros) aus einem kleineren Satz von Quellen generiert wird und bei dem das Programm nicht ständig Dinge herausfindet, von denen der Programmierer weiß, dass sie ziemlich vorhersehbar sind. Mit anderen Worten, "interpretieren" Sie die Abfolge der zu erledigenden Dinge nicht, "kompilieren" Sie sie nicht.

  • Das Redesign wird durchgeführt, der Quellcode um den Faktor 4 verkleinert und die Zeit auf 10 Sekunden reduziert.

Jetzt, da es so schnell geht, ist es schwer zu probieren, also gebe ich ihm zehnmal so viel Arbeit, aber die folgenden Zeiten basieren auf der ursprünglichen Arbeitsbelastung.

  • Mehr Diagnose zeigt, dass es Zeit in der Warteschlangenverwaltung verbringt. Das Einkleiden verkürzt die Zeit auf 7 Sekunden.

  • Jetzt ist der diagnostische Druck, den ich gemacht habe, ein großer Zeitfresser. Spülen Sie das - 4 Sekunden.

  • Die größten Zeitnehmer sind jetzt Anrufe nach malloc und free . Objekte recyceln - 2,6 Sekunden.

  • Wenn ich mit dem Beispiel fortfahre, finde ich immer noch Vorgänge, die nicht unbedingt erforderlich sind - 1,1 Sekunden.

Gesamtbeschleunigungsfaktor: 43,6

Nun sind keine zwei Programme gleich, aber in Nicht-Spielzeug-Software habe ich immer einen Fortschritt wie diesen gesehen. Zuerst bekommt man die einfachen Dinge und dann die schwierigeren, bis man zu einem Punkt kommt, an dem die Renditen sinken. Die gewonnenen Erkenntnisse können dann durchaus zu einer Neugestaltung führen und eine neue Runde von Beschleunigungen einleiten, bis Sie wieder auf sinkende Renditen stoßen. Dies ist der Punkt, an dem es sinnvoll sein könnte, sich zu fragen, ob ++i Oder i++ Oder for(;;) oder while(1) schneller sind: die Art der Fragen Ich sehe so oft Stack Overflow.

P.S. Es kann sich fragen, warum ich keinen Profiler verwendet habe. Die Antwort ist, dass fast jedes dieser "Probleme" eine Funktionsaufrufstelle war, die Stichproben punktgenau stapelt. Profiler kommen auch heute kaum auf die Idee, dass Anweisungen und Aufrufanweisungen wichtiger zu lokalisieren und einfacher zu beheben sind als ganze Funktionen.

Eigentlich habe ich einen Profiler dafür erstellt, aber für eine echte Intimität mit dem Code gibt es keinen Ersatz dafür, dass Sie Ihre Finger richtig in den Code stecken. Es ist kein Problem, dass die Anzahl der Proben gering ist, da keines der gefundenen Probleme so gering ist, dass sie leicht übersehen werden.

ADDED: Jerryjvl hat einige Beispiele angefordert. Hier ist das erste Problem. Es besteht aus einer kleinen Anzahl separater Codezeilen, die zusammen die Hälfte der Zeit in Anspruch nehmen:

 /* 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)

Diese verwendeten den Listencluster ILST (ähnlich einer Listenklasse). Sie werden auf die übliche Weise implementiert, wobei "Informationen verbergen" bedeutet, dass sich die Benutzer der Klasse nicht darum kümmern müssen, wie sie implementiert wurden. Als diese Zeilen geschrieben wurden (aus ungefähr 800 Codezeilen), dachte man nicht daran, dass dies ein "Flaschenhals" sein könnte (ich hasse dieses Wort). Sie sind einfach die empfohlene Vorgehensweise. Es ist leicht zu sagen im Nachhinein , dass diese hätten vermieden werden sollen, aber nach meiner Erfahrung alle Leistungsprobleme sind so. Im Allgemeinen sollten Sie versuchen, Leistungsprobleme zu vermeiden. Es ist sogar besser, diejenigen zu finden und zu reparieren, die erstellt wurden, obwohl sie (im Nachhinein) "hätten vermieden werden müssen". Ich hoffe das gibt ein bisschen Geschmack.

Hier ist das zweite Problem in zwei separaten Zeilen:

 /* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)

Hierbei werden Listen erstellt, indem Elemente an ihre Enden angehängt werden. (Der Fix bestand darin, die Elemente in Arrays zu sammeln und die Listen auf einmal zu erstellen.) Das Interessante ist, dass diese Anweisungen nur 3/48 der ursprünglichen Zeit kosten (dh sich auf dem Call-Stack befanden), also nicht in Tatsächlich ein großes Problem am Anfang . Nach dem Beseitigen des ersten Problems kosteten sie jedoch 3/20 der Zeit und waren somit ein "größerer Fisch". Im Allgemeinen ist es so.

Ich könnte hinzufügen, dass dieses Projekt von einem echten Projekt, an dem ich mitgewirkt habe, abgeleitet wurde. In diesem Projekt waren die Leistungsprobleme weitaus dramatischer (wie auch die Geschwindigkeitssteigerungen), z. B. der Aufruf einer Datenbankzugriffsroutine innerhalb einer inneren Schleife, um festzustellen, ob eine Aufgabe abgeschlossen wurde.

HINZUFÜGENDE VERWEISE: Der ursprüngliche und überarbeitete Quellcode ist in www.ddj.com für 1993 in Datei 9311.Zip, Dateien slug.asc und slug.Zip enthalten.

EDIT 2011/11/26: Es gibt jetzt ein SourceForge-Projekt mit Quellcode in Visual C++ und einer ausführlichen Beschreibung der Optimierung. Es wird nur die erste Hälfte des oben beschriebenen Szenarios durchlaufen, und es folgt nicht genau der gleichen Sequenz, erhält jedoch eine Beschleunigung von 2-3 Größenordnungen.

419
Mike Dunlavey

Vorschläge:

  • Vorberechnen statt neu berechnen: Bei Schleifen oder wiederholten Aufrufen, die Berechnungen mit einem relativ begrenzten Eingabebereich enthalten, sollten Sie eine Suche (Array oder Wörterbuch) durchführen, die das Ergebnis dieser Berechnung für enthält Alle Werte im gültigen Eingabebereich. Verwenden Sie stattdessen eine einfache Suche innerhalb des Algorithmus.
    Nachteile : Wenn nur wenige der vorberechneten Werte tatsächlich verwendet werden, kann dies die Situation verschlimmern, und auch die Suche kann erheblich sein Erinnerung.
  • Verwenden Sie keine Bibliotheksmethoden: Die meisten Bibliotheken müssen geschrieben werden, um in einer Vielzahl von Szenarien ordnungsgemäß zu funktionieren, und führen Nullprüfungen für Parameter usw. durch. Durch erneutes Implementieren einer Methode können Sie dies möglicherweise Entfernen Sie eine Menge Logik, die nicht genau unter den Umständen angewendet wird, unter denen Sie sie verwenden.
    Nachteile : Das Schreiben von zusätzlichem Code bedeutet mehr Oberfläche für Fehler.
  • Benutze Bibliotheksmethoden: Um mir selbst zu widersprechen, werden Sprachbibliotheken von Leuten geschrieben, die viel schlauer sind als du oder ich; Wahrscheinlich haben sie es besser und schneller gemacht. Implementieren Sie es nicht selbst, es sei denn, Sie können es tatsächlich schneller machen (d. H. Messen Sie immer!)
  • Cheat: In einigen Fällen kann es vorkommen, dass eine genaue Berechnung für Ihr Problem vorhanden ist, Sie jedoch nicht "genau" benötigen. Manchmal ist eine Annäherung "gut genug" und viel schneller im Geschäft. Fragen Sie sich, ist es wirklich wichtig, ob die Antwort um 1% falsch ist? 5%? sogar 10%?
    Nachteile : Nun ... die Antwort wird nicht genau sein.
186
jerryjvl

Wenn Sie die Leistung nicht mehr verbessern können, prüfen Sie, ob Sie stattdessen die wahrgenommene Leistung verbessern können.

Sie sind möglicherweise nicht in der Lage, Ihren fooCalc-Algorithmus schneller zu machen, aber häufig gibt es Möglichkeiten, die Reaktion Ihrer Anwendung auf den Benutzer zu verbessern.

Einige Beispiele:

  • nehmen Sie vorweg, was der Benutzer anfordert, und beginnen Sie dann, daran zu arbeiten
  • anzeigen der Ergebnisse, sobald sie eingehen, anstatt am Ende alle auf einmal
  • Genaue Fortschrittsanzeige

Dadurch wird Ihr Programm nicht schneller, aber Ihre Benutzer werden möglicherweise mit der Geschwindigkeit, die Sie haben, zufriedener.

163
kenj0418

Ich verbringe den größten Teil meines Lebens nur an diesem Ort. Mit den großen Strichen können Sie Ihren Profiler ausführen und aufzeichnen lassen:

  • Cache misses. Der Datencache ist in den meisten Programmen die häufigste Quelle für Verzögerungen. Verbessern Sie die Cache-Trefferrate, indem Sie anstößige Datenstrukturen neu organisieren, um eine bessere Lokalität zu erzielen. Packen Sie Strukturen und numerische Typen nach unten, um verschwendete Bytes (und damit verschwendete Cache-Abrufe) zu vermeiden. Prefetch-Daten, wo immer dies möglich ist, um Stalls zu reduzieren.
  • Load-Hit-Stores. Compiler-Annahmen zum Pointer-Aliasing und zu Fällen, in denen Daten zwischen getrennten Registersätzen über den Speicher verschoben werden, können ein bestimmtes pathologisches Verhalten verursachen, durch das die gesamte CPU-Pipeline bei einem Ladevorgang gelöscht wird. Finden Sie Orte, an denen Floats, Vektoren und Ints gegeneinander geworfen werden, und beseitigen Sie sie. Verwenden __restrict großzügig, um dem Compiler ein Aliasing zu versprechen.
  • Mikrocodierte Operationen. Die meisten Prozessoren haben einige Vorgänge, die nicht per Pipeline ausgeführt werden können, sondern führen eine winzige Unterroutine aus, die im ROM gespeichert ist. Beispiele auf dem PowerPC sind ganzzahliges Multiplizieren, Dividieren und Verschieben nach variablem Betrag. Das Problem ist, dass die gesamte Pipeline nicht mehr reagiert, während diese Operation ausgeführt wird. Versuchen Sie, die Verwendung dieser Operationen zu unterbinden oder sie zumindest in ihre einzelnen Pipeline-Operationen aufzuteilen, damit Sie den Vorteil eines superskalaren Versands für alle anderen Aufgaben Ihres Programms erhalten.
  • Branch mispredicts. Auch diese leeren die Pipeline. Suchen Sie nach Fällen, in denen die CPU viel Zeit damit verbringt, die Pipe nach einer Verzweigung wieder aufzufüllen, und verwenden Sie, falls verfügbar, die Verzweigungshinweise, um die Vorhersage häufiger korrekt zu machen. Oder besser noch, ersetze Zweige durch bedingte Bewegungen, wo immer dies möglich ist, insbesondere nach Gleitkommaoperationen, da ihre Pipe normalerweise tiefer ist und die Bedingungsflags nach fcmp gelesen werden können einen Stall verursachen.
  • Sequentielle Gleitkommaoperationen. Machen Sie diese SIMD.

Und noch etwas, was ich gerne mache:

  • Stellen Sie Ihren Compiler so ein, dass Assembly-Listen ausgegeben werden und überprüfen Sie, was er für die Hotspot-Funktionen in Ihrem Code ausgibt. All diese cleveren Optimierungen, die "ein guter Compiler automatisch für Sie erledigen kann"? Wahrscheinlich tut Ihr tatsächlicher Compiler dies nicht. Ich habe gesehen, wie GCC wirklich WTF-Code ausgegeben hat.
137
Crashworks

Wirf mehr Hardware drauf!

78
sisve

Weitere Vorschläge:

  • Vermeiden Sie E/A: Jedes E/A (Datenträger, Netzwerk, Ports usw.) ist immer viel langsamer als jeder Code, der Berechnungen durchführt. Beseitigen Sie daher alle E/A, die ausgeführt werden das brauchst du nicht unbedingt.

  • Move I/O up-front: Laden Sie alle Daten, die Sie für eine Berechnung benötigen, im Voraus, damit Sie innerhalb des Kerns eines kritischen Algorithmus keine wiederholten I/O-Wartezeiten haben (und möglicherweise infolgedessen können wiederholte Festplattensuchvorgänge beim Laden aller Daten in einem Treffer das Suchen vermeiden).

  • Delay I/O: Schreiben Sie Ihre Ergebnisse erst nach Abschluss der Berechnung auf, speichern Sie sie in einer Datenstruktur und geben Sie sie am Ende der harten Arbeit auf einmal aus.

  • Threaded I/O: Für diejenigen, die es wagen, kombinieren Sie 'I/O up-front' oder 'Delay I/O' mit der tatsächlichen Berechnung, indem Sie die Last in einen parallelen Thread verschieben, so dass Sie dabei sind Wenn Sie mehr Daten laden, können Sie mit den bereits vorhandenen Daten an einer Berechnung arbeiten, oder während Sie den nächsten Datenstapel berechnen, können Sie gleichzeitig die Ergebnisse des letzten Stapels ausschreiben.

58
jerryjvl

Da viele der Leistungsprobleme mit Datenbankproblemen zusammenhängen, möchte ich Ihnen einige Besonderheiten beim Optimieren von Abfragen und gespeicherten Prozeduren erläutern.

Vermeiden Sie Cursor in den meisten Datenbanken. Vermeiden Sie auch Schleifen. In den meisten Fällen sollte der Datenzugriff satzbasiert und nicht satzweise erfolgen. Dazu gehört, dass Sie eine gespeicherte Prozedur für einen einzelnen Datensatz nicht wiederverwenden, wenn Sie 1.000.000 Datensätze gleichzeitig einfügen möchten.

Verwenden Sie niemals select *, sondern geben Sie nur die Felder zurück, die Sie tatsächlich benötigen. Dies gilt insbesondere dann, wenn Verknüpfungen vorhanden sind, da die Verknüpfungsfelder wiederholt werden und somit sowohl den Server als auch das Netzwerk unnötig belasten.

Vermeiden Sie die Verwendung korrelierter Unterabfragen. Verwenden Sie Joins (einschließlich Joins zu abgeleiteten Tabellen, wo dies möglich ist) (ich weiß, dass dies für Microsoft SQL Server zutrifft, aber testen Sie die Ratschläge, wenn Sie ein anderes Backend verwenden).

Index, Index, Index. Und aktualisieren Sie diese Statistiken, falls für Ihre Datenbank zutreffend.

Stellen Sie die Abfrage sargable . Das bedeutet, Dinge zu vermeiden, die es unmöglich machen, die Indizes zu verwenden, wie die Verwendung eines Platzhalters im ersten Zeichen einer Like-Klausel oder einer Funktion im Join oder als linker Teil einer where-Anweisung.

Verwenden Sie die richtigen Datentypen. Es ist schneller, Datumsberechnungen für ein Datumsfeld durchzuführen, als zu versuchen, einen String-Datentyp in einen Datums-Datentyp zu konvertieren, und dann die Berechnung durchzuführen.

Niemals eine Schlaufe in einen Abzug stecken!

In den meisten Datenbanken können Sie überprüfen, wie die Abfrage ausgeführt wird. In Microsoft SQL Server wird dies als Ausführungsplan bezeichnet. Überprüfen Sie diese zuerst, um festzustellen, wo die Problembereiche liegen.

Überlegen Sie, wie oft die Abfrage ausgeführt wird und wie lange sie ausgeführt werden muss, um zu bestimmen, was optimiert werden muss. Manchmal können Sie durch geringfügige Änderungen an einer Abfrage, die millionenfach am Tag ausgeführt wird, mehr Leistung erzielen, als durch das Löschen einer langen_Abfrage, die nur einmal im Monat ausgeführt wird.

Verwenden Sie eine Art Profiler-Tool, um herauszufinden, was wirklich an die und von der Datenbank gesendet wird. Ich kann mich an eine Zeit erinnern, in der wir nicht herausfinden konnten, warum das Laden der Seite so langsam war, als die gespeicherte Prozedur schnell war, und durch Profilerstellung herausgefunden haben, dass die Webseite die Abfrage viele Male anstatt einmal angefordert hat.

Der Profiler hilft Ihnen auch zu finden, wer wen blockiert. Einige Abfragen, die schnell ausgeführt werden, während sie alleine ausgeführt werden, können aufgrund von Sperren anderer Abfragen sehr langsam werden.

47
HLGEM

Der wichtigste heutige begrenzende Faktor ist begrenzte Speicherbandbreite. Multicores machen dies nur noch schlimmer, da die Bandbreite zwischen den Kernen geteilt wird. Auch die begrenzte Chipfläche, die für die Implementierung von Caches vorgesehen ist, wird auf die Kerne und Threads aufgeteilt, wodurch sich dieses Problem noch weiter verschlechtert. Schließlich nimmt auch die Inter-Chip-Signalisierung, die erforderlich ist, um die verschiedenen Caches kohärent zu halten, mit einer erhöhten Anzahl von Kernen zu. Dies fügt auch eine Strafe hinzu.

Dies sind die Effekte, die Sie verwalten müssen. Manchmal durch Mikromanagement Ihres Codes, manchmal durch sorgfältige Überlegung und Umgestaltung.

Viele Kommentare erwähnen bereits Cache-freundlichen Code. Es gibt mindestens zwei verschiedene Geschmacksrichtungen:

  • Vermeiden Sie Latenzen beim Speicherabruf.
  • Verringern Sie den Speicherbusdruck (Bandbreite).

Das erste Problem besteht insbesondere darin, Ihre Datenzugriffsmuster regelmäßiger zu gestalten, damit der Hardware-Prefetcher effizient arbeiten kann. Vermeiden Sie dynamische Speicherzuordnungen, die Ihre Datenobjekte im Arbeitsspeicher verteilen. Verwenden Sie lineare Container anstelle von verknüpften Listen, Hashes und Bäumen.

Das zweite Problem hat mit der Verbesserung der Datenwiederverwendung zu tun. Ändern Sie Ihre Algorithmen so, dass sie Teilmengen Ihrer Daten verarbeiten, die in den verfügbaren Cache passen, und verwenden Sie diese Daten so weit wie möglich, während sie sich noch im Cache befinden.

Wenn Sie die Daten enger packen und sicherstellen, dass Sie alle Daten in den Cache-Zeilen in den Hot-Loops verwenden, können Sie diese anderen Effekte vermeiden und weitere nützliche Daten einfügen der Cache.

29
Mats N
  • Auf welcher Hardware laufen Sie? Können Sie plattformspezifische Optimierungen (z. B. Vektorisierung) verwenden?
  • Können Sie einen besseren Compiler bekommen? Z.B. von GCC auf Intel umsteigen?
  • Können Sie Ihren Algorithmus parallel ausführen?
  • Können Sie Cache-Ausfälle reduzieren, indem Sie Daten neu organisieren?
  • Können Sie Asserts deaktivieren?
  • Micro-Optimize für Ihren Compiler und Ihre Plattform. Im Stil von "An einem if/else steht die häufigste Aussage an erster Stelle"
25
Johan Kotlinski

Sie sollten wahrscheinlich die "Google-Perspektive" in Betracht ziehen, dh bestimmen, wie Ihre Anwendung weitgehend parallelisiert und gleichzeitig ausgeführt werden kann. Dies bedeutet zwangsläufig auch, dass Sie Ihre Anwendung irgendwann auf verschiedene Computer und Netzwerke verteilen müssen, damit sie im Idealfall nahezu linear skaliert werden kann mit der Hardware, die Sie darauf werfen.

Andererseits sind die Google-Leute auch dafür bekannt, dass sie viel Personal und Ressourcen einsetzen, um einige der Probleme in Projekten, Tools und Infrastrukturen zu lösen, die sie verwenden, wie zum Beispiel vollständige Programmoptimierung für gcc durch ein engagiertes Team von Ingenieuren, die GCC-Interna hacken, um sie auf typische Anwendungsszenarien von Google vorzubereiten.

In ähnlicher Weise bedeutet das Profilieren einer Anwendung nicht mehr nur das Profilieren des Programmcodes, sondern auch aller umgebenden Systeme und Infrastrukturen (z. B. Netzwerke, Switches, Server, RAID-Arrays), um Redundanzen und Optimierungspotenziale aus Sicht eines Systems zu identifizieren.

16
none

Obwohl mir die Antwort von Mike Dunlavey gefällt, ist sie in der Tat eine großartige Antwort mit unterstützendem Beispiel. Ich denke, sie könnte sehr einfach so ausgedrückt werden:

Finden Sie zuerst heraus, was die meiste Zeit in Anspruch nimmt, und verstehen Sie, warum.

Es ist der Identifikationsprozess der Zeitfresser, der Ihnen hilft zu verstehen, wo Sie Ihren Algorithmus verfeinern müssen. Dies ist die einzige umfassende sprachunabhängige Antwort, die ich auf ein Problem finden kann, das bereits vollständig optimiert werden soll. Angenommen, Sie möchten in Ihrem Streben nach Geschwindigkeit architekturunabhängig sein.

Während also der Algorithmus optimiert werden kann, kann die Implementierung davon nicht sein. Anhand der Identifikation können Sie erkennen, welcher Teil welcher ist: Algorithmus oder Implementierung. Was auch immer die meiste Zeit frisst, ist Ihr Hauptkandidat für eine Überprüfung. Aber da Sie sagen, Sie möchten die letzten paar Prozent herausdrücken, möchten Sie vielleicht auch die kleineren Teile untersuchen, die Sie zuerst nicht so genau untersucht haben.

Ein bisschen Ausprobieren mit Leistungsdaten auf unterschiedlichen Wegen zur Implementierung derselben Lösung oder potenziell unterschiedlichen Algorithmen kann schließlich Erkenntnisse liefern, die helfen, Zeitverschwender und Zeitersparnisse zu identifizieren.

HPH, asoudmove.

16
asoundmove
  • Inline-Routinen (eliminieren Call/Return und Parameter-Pushing)
  • Versuche Tests/Schalter mit Tabellensuchen zu eliminieren (wenn sie schneller sind)
  • Rollen Sie die Loops (Duffs Gerät) so weit ab, bis sie gerade in den CPU-Cache passen
  • Lokalisieren Sie den Speicherzugriff, um den Cache nicht zu belasten
  • Lokalisieren Sie verwandte Berechnungen, wenn der Optimierer dies nicht bereits tut
  • Beseitigen Sie Schleifeninvarianten, wenn der Optimierer dies nicht bereits tut
15
plinth

Teilen und Erobern

Wenn das zu verarbeitende Dataset zu groß ist, schleifen Sie über Teile davon. Wenn Sie Ihren Code richtig gemacht haben, sollte die Implementierung einfach sein. Wenn Sie ein monolithisches Programm haben, wissen Sie es jetzt besser.

12
MPelletier
  • Wenn Sie zu dem Punkt kommen, dass Sie effiziente Algorithmen verwenden, ist es eine Frage, was Sie mehr benötigen Geschwindigkeit oder Speicher. Verwenden Sie die Zwischenspeicherung, um schneller im Speicher zu bezahlen, oder verwenden Sie Berechnungen, um den Speicherbedarf zu verringern.
  • Wenn möglich (und kostengünstiger) werfen Sie die Hardware auf das Problem - schnellere CPU, mehr Speicher oder HD könnten das Problem schneller lösen, als wenn Sie versuchen, es zu codieren.
  • Verwenden Sie Parallelisierung wenn möglich - führen Sie einen Teil des Codes auf mehreren Threads aus.
  • Verwenden Sie das richtige Werkzeug für den Job. Einige Programmiersprachen erstellen effizienteren Code, indem verwalteter Code (z. B. Java/.NET) die Entwicklung beschleunigt, native Programmiersprachen jedoch schnelleren Code.
  • Mikrooptimierung. Nur wenn dies zutrifft, können Sie optimierte Assembly verwenden, um kleine Codestücke zu beschleunigen. Durch die Verwendung von SSE-/Vektoroptimierungen an den richtigen Stellen kann die Leistung erheblich gesteigert werden.
12
Dror Helper

Erfahren Sie zunächst, wie in mehreren vorherigen Antworten erwähnt, was Ihre Leistung beeinträchtigt - ob es sich um Speicher, Prozessor, Netzwerk, Datenbank oder etwas anderes handelt. Abhängig davon ...

  • ... wenn es sich um Erinnerung handelt - finden Sie eines der Bücher, die Knuth vor langer Zeit geschrieben hat, eine der Serien "Die Kunst der Computerprogrammierung". Höchstwahrscheinlich geht es um Sortieren und Suchen - wenn mein Gedächtnis falsch ist, müssen Sie herausfinden, in welchem ​​Format er über den Umgang mit langsamer Banddatenspeicherung spricht. Verwandeln Sie geistig sein Speicher/Band Paar in Ihr Paar aus Cache/Hauptspeicher (bzw. in ein Paar aus L1/L2-Cache). Lernen Sie alle von ihm beschriebenen Tricks kennen. Wenn Sie etwas finden, das Ihr Problem löst, beauftragen Sie einen professionellen Informatiker mit der Durchführung einer professionellen Recherche. Wenn Ihr Gedächtnisproblem zufällig mit FFT zusammenhängt (Cache-Fehlschläge bei bitumgekehrten Indizes bei Radix-2-Schmetterlingen), stellen Sie keinen Wissenschaftler ein. Optimieren Sie stattdessen die Pässe manuell nacheinander, bis Sie entweder gewinnen oder gewinnen zur Sackgasse. Sie erwähnten drücken Sie bis zu den letzten Prozent aus richtig? Wenn es wenige ist, werden Sie höchstwahrscheinlich gewinnen.

  • ... wenn es sich um einen Prozessor handelt - wechseln Sie in die Assemblersprache. Spezifikation des Studienprozessors - was Zecken benötigt, VLIW, SIMD. Funktionsaufrufe sind höchstwahrscheinlich austauschbare Zeckenfresser. Lernen Sie Schleifenumwandlungen - Pipeline, Abrollen. Multiplikationen und Divisionen können durch Bitverschiebungen ersetzt/interpoliert werden (Multiplikationen durch kleine ganze Zahlen können durch Additionen ersetzt werden). Probieren Sie Tricks mit kürzeren Daten aus - wenn Sie Glück haben, kann ein Befehl mit 64 Bit durch zwei mit 32 oder sogar 4 mit 16 oder 8 mit 8 Bit ersetzt werden. Versuchen Sie auch länger Daten - z. B. Ihre Float-Berechnungen könnten bei bestimmten Prozessoren langsamer ausfallen als die doppelten. Wenn du trigonometrisches Zeug hast, bekämpfe es mit vorberechneten Tabellen. Beachten Sie auch, dass Sinus kleinen Werts durch diesen Wert ersetzt werden kann, wenn der Genauigkeitsverlust innerhalb der zulässigen Grenzen liegt.

  • ... wenn es sich um ein Netzwerk handelt - denken Sie daran, Daten zu komprimieren, die Sie über das Netzwerk übertragen. Ersetzen Sie die XML-Übertragung durch eine Binärdatei. Studienprotokolle. Versuchen Sie UDP anstelle von TCP, wenn Sie irgendwie mit Datenverlust umgehen können.

  • ... wenn es sich um eine Datenbank handelt, gehen Sie in ein Datenbankforum und fragen Sie um Rat. Speicherinternes Datenraster, Optimierung des Abfrageplans usw. usw. usw.

HTH :)

11
gnat

Caching! Ein billiger Weg (in Programmierbemühungen), um fast alles schneller zu machen, besteht darin, jedem Datenbewegungsbereich Ihres Programms eine Caching-Abstraktionsschicht hinzuzufügen. Sei es I/O oder nur Übergabe/Erstellung von Objekten oder Strukturen. Oft ist es einfach, Caches zu Factory-Klassen und Readern/Writern hinzuzufügen.

Manchmal bringt der Cache nicht viel, aber es ist eine einfache Methode, Caching überall hinzuzufügen und es dann zu deaktivieren, wo es nicht hilft. Ich habe oft festgestellt, dass dies eine enorme Leistung bringt, ohne dass der Code einer Mikroanalyse unterzogen werden muss.

9
Killroy

Ich denke, das wurde schon anders gesagt. Wenn Sie sich jedoch mit einem prozessorintensiven Algorithmus beschäftigen, sollten Sie alles innerhalb der innersten Schleife auf Kosten von allem anderen vereinfachen.

Für manche mag das offensichtlich erscheinen, aber ich versuche mich darauf zu konzentrieren, unabhängig von der Sprache, mit der ich arbeite. Wenn Sie zum Beispiel mit verschachtelten Schleifen arbeiten und die Möglichkeit finden, Code auf eine niedrigere Ebene zu bringen, können Sie den Code in einigen Fällen drastisch beschleunigen. Als weiteres Beispiel gibt es die kleinen Dinge, über die man nachdenken sollte, wenn man mit ganzen Zahlen anstelle von Gleitkommavariablen arbeitet und wenn man kann, Multiplikation statt Division verwendet. Auch dies sind Dinge, die für Ihre innerste Schleife in Betracht gezogen werden sollten.

Manchmal kann es von Vorteil sein, wenn Sie Ihre mathematischen Operationen an einer Ganzzahl innerhalb der inneren Schleife ausführen und sie anschließend auf eine Gleitkommavariable verkleinern, mit der Sie anschließend arbeiten können. Dies ist ein Beispiel für Geschwindigkeitsverluste in einem Abschnitt, um die Geschwindigkeit in einem anderen zu verbessern, aber in einigen Fällen kann sich die Auszahlung durchaus lohnen.

8
Steve Wortham

Ich habe einige Zeit mit der Optimierung von Client/Server-Geschäftssystemen verbracht, die über Netzwerke mit geringer Bandbreite und langer Latenz (z. B. Satellit, Remote, Offshore) betrieben werden, und konnte mit einem relativ wiederholbaren Prozess einige dramatische Leistungsverbesserungen erzielen.

  • Measure: Beginnen Sie mit dem Verständnis der zugrunde liegenden Kapazität und Topologie des Netzwerks. Sprechen Sie mit den relevanten Netzwerkmitarbeitern im Unternehmen und verwenden Sie grundlegende Tools wie Ping und Traceroute, um (mindestens) die Netzwerklatenz von jedem Clientstandort während typischer Betriebsperioden zu ermitteln. Nehmen Sie als Nächstes genaue Zeitmessungen für bestimmte Endbenutzerfunktionen vor, die die problematischen Symptome anzeigen. Notieren Sie alle diese Messungen zusammen mit ihren Standorten, Datumsangaben und Uhrzeiten. Erwägen Sie, die Funktionalität zum Testen der Netzwerkleistung von Endbenutzern in Ihre Clientanwendung zu integrieren, damit Ihre Hauptbenutzer am Verbesserungsprozess teilnehmen können. Wenn Sie sie auf diese Weise stärken, kann dies eine große psychologische Auswirkung haben, wenn Sie mit Benutzern zu tun haben, die von einem System mit schlechter Leistung frustriert sind.

  • Analyse: Verwenden Sie alle verfügbaren Protokollierungsmethoden, um genau festzustellen, welche Daten während der Ausführung der betroffenen Vorgänge gesendet und empfangen werden. Idealerweise kann Ihre Anwendung Daten erfassen, die sowohl vom Client als auch vom Server gesendet und empfangen werden. Wenn diese auch Zeitstempel enthalten, ist dies noch besser. Wenn nicht genügend Protokollierung verfügbar ist (z. B. geschlossenes System oder Unfähigkeit, Änderungen in einer Produktionsumgebung bereitzustellen), verwenden Sie einen Netzwerk-Sniffer und stellen Sie sicher, dass Sie wirklich verstehen, was auf Netzwerkebene vor sich geht.

  • Cache: Suchen Sie nach Fällen, in denen statische oder selten geänderte Daten wiederholt übertragen werden, und überlegen Sie sich eine geeignete Caching-Strategie. Typische Beispiele sind "Auswahllisten" -Werte oder andere "Referenzobjekte", die in einigen Geschäftsanwendungen überraschend groß sein können. In vielen Fällen können Benutzer akzeptieren, dass sie die Anwendung neu starten oder aktualisieren müssen, um selten aktualisierte Daten zu aktualisieren. Dies gilt insbesondere dann, wenn die Anzeige häufig verwendeter Benutzeroberflächenelemente erheblich länger dauert. Vergewissern Sie sich, dass Sie das tatsächliche Verhalten der bereits implementierten Caching-Elemente verstehen. Viele gängige Caching-Methoden (z. B. HTTP ETag) erfordern immer noch einen Netzwerk-Roundtrip, um die Konsistenz zu gewährleisten. Wenn die Netzwerk-Latenz teuer ist, können Sie dies möglicherweise ganz vermeiden ein anderer Caching-Ansatz.

  • Parallelisieren: Suchen Sie nach sequentiellen Transaktionen, die logischerweise nicht streng sequentiell ausgegeben werden müssen, und überarbeiten Sie das System, um sie parallel auszugeben. Ich habe mich mit einem Fall befasst, in dem eine End-to-End-Anforderung eine inhärente Netzwerkverzögerung von ~ 2 s aufwies, was kein Problem für eine einzelne Transaktion war, aber wenn 6 aufeinanderfolgende Roundtrips von 2 s erforderlich waren, bevor der Benutzer die Kontrolle über die Clientanwendung wiedererlangte Es wurde eine riesige Quelle der Frustration. Die Entdeckung, dass diese Transaktionen tatsächlich unabhängig waren, ermöglichte die parallele Ausführung, wodurch die Verzögerung für den Endbenutzer auf nahezu die Kosten einer einzelnen Hin- und Rückfahrt reduziert wurde.

  • Kombinieren: Wenn sequentielle Anforderungen sequentiell ausgeführt werden müssen , suchen Sie nach Möglichkeiten, sie zu einer einzigen umfassenderen Anforderung zu kombinieren. Typische Beispiele sind das Erstellen neuer Entitäten, gefolgt von der Aufforderung, diese Entitäten mit anderen vorhandenen Entitäten in Beziehung zu setzen.

  • Komprimieren: Suchen Sie nach Möglichkeiten, die Komprimierung der Nutzlast zu nutzen, indem Sie entweder eine Textform durch eine Binärform ersetzen oder die eigentliche Komprimierungstechnologie verwenden. Viele moderne (d. H. Innerhalb eines Jahrzehnts) Technologie-Stacks unterstützen dies nahezu transparent. Stellen Sie daher sicher, dass es konfiguriert ist. Ich war oft überrascht von den erheblichen Auswirkungen der Komprimierung, bei denen es offensichtlich war, dass das Problem im Grunde eher die Latenz als die Bandbreite war. Dabei stellte ich fest, dass die Transaktion in ein einzelnes Paket passen oder auf andere Weise Paketverluste vermeiden und daher eine Übergröße aufweisen konnte Auswirkungen auf die Leistung.

  • Wiederholen: Kehren Sie zum Anfang zurück und messen Sie Ihre Vorgänge (an denselben Orten und zu denselben Zeiten) mit den Verbesserungen, zeichnen Sie Ihre Ergebnisse auf und berichten Sie sie. Wie bei allen Optimierungen wurden möglicherweise einige Probleme gelöst, während andere aufgedeckt wurden, die jetzt dominieren.

In den obigen Schritten konzentriere ich mich auf den anwendungsbezogenen Optimierungsprozess, aber Sie müssen natürlich sicherstellen, dass das zugrunde liegende Netzwerk selbst auf die effizienteste Weise konfiguriert ist, um auch Ihre Anwendung zu unterstützen. Binden Sie die Netzwerkspezialisten in das Geschäft ein und stellen Sie fest, ob sie Kapazitätsverbesserungen, QoS, Netzwerkkomprimierung oder andere Techniken anwenden können, um das Problem zu beheben. In der Regel werden sie die Anforderungen Ihrer Anwendung nicht verstehen. Daher ist es wichtig, dass Sie (nach dem Analyseschritt) in der Lage sind, diese mit ihnen zu besprechen und das Geschäftsmodell für die Kosten zu erstellen, die ihnen entstehen . Ich habe Fälle erlebt, in denen fehlerhafte Netzwerkkonfigurationen dazu führten, dass die Anwendungsdaten über eine langsame Satellitenverbindung und nicht über eine Überlandverbindung übertragen wurden, einfach weil ein TCP - Port verwendet wurde, der von der nicht "gut bekannt" war Netzwerkspezialisten; Die Behebung eines solchen Problems kann sich offensichtlich dramatisch auf die Leistung auswirken, da überhaupt kein Software-Code oder Konfigurationsänderungen erforderlich sind.

8
Pat

Nicht annähernd so tiefgreifend oder komplex wie die vorherigen Antworten, aber hier ist Folgendes:

  • offensichtlich: trocken
  • führen Sie Schleifen rückwärts aus, damit Sie immer mit 0 und nicht mit einer Variablen vergleichen
  • verwenden Sie bitweise Operatoren, wann immer Sie können
  • teilen Sie sich wiederholenden Code in Module/Funktionen
  • objekte zwischenspeichern
  • lokale Variablen haben einen geringen Leistungsvorteil
  • begrenzen Sie die Manipulation von Zeichenfolgen so weit wie möglich
7
Aaron

Es ist sehr schwierig, eine generische Antwort auf diese Frage zu geben. Es hängt wirklich von Ihrer Problemdomäne und der technischen Implementierung ab. Eine allgemeine Technik, die ziemlich sprachneutral ist: Identifizieren Sie Code-Hotspots, die nicht beseitigt werden können, und optimieren Sie Assembler-Code von Hand.

7
dschwarz

Die letzten paar% sind sehr CPU- und anwendungsabhängig ....

  • cache-Architekturen unterscheiden sich, einige Chips haben On-Chip RAM Sie können direkt abbilden, ARMs (manchmal) haben eine Vektoreinheit, SH4 ist ein nützlicher Matrix-Opcode. Gibt es ein GPU - Vielleicht ist ein Shader der richtige Weg. TMS32 's reagieren sehr empfindlich auf Verzweigungen innerhalb von Schleifen (also trennen Sie Schleifen und verschieben Sie Bedingungen nach außen, wenn möglich).

Die Liste geht weiter ... Aber diese Art von Dingen sind wirklich der letzte Ausweg ...

Erstellen Sie für x86, und führen Sie Valgrind /Cachegrind für eine ordnungsgemäße Leistungsprofilerstellung für den Code aus. Oder Texas Instruments ' CCStudio hat einen süßen Profiler. Dann wissen Sie genau, worauf Sie sich konzentrieren müssen ...

7
Cwaig

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

Bei allen nicht-Offline-Projekten mit der besten Software und der besten Hardware wird bei einem schwachen Durchsatz diese dünne Linie die Daten komprimieren und Verzögerungen verursachen, wenn auch in Millisekunden ... aber wenn Sie von den letzten Tropfen sprechen Dies sind einige Tropfen, die rund um die Uhr für jedes gesendete oder empfangene Paket gewonnen werden.

7
Sam

Hinzufügen dieser Antwort, da ich sie nicht in allen anderen gesehen habe.

Minimieren Sie die implizite Konvertierung zwischen Typen und Vorzeichen:

Dies gilt zumindest für C/C++. Auch wenn Sie bereits denken keine Konvertierungen vorgenommen haben - manchmal empfiehlt es sich, Compiler-Warnungen zu Funktionen hinzuzufügen, die Leistung erfordern, insbesondere, auf Konvertierungen innerhalb von Schleifen zu achten .

GCC-spezifisch: Sie können dies testen, indem Sie Ihrem Code einige ausführliche Pragmas hinzufügen.

#ifdef __GNUC__
#  pragma GCC diagnostic Push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

Ich habe Fälle gesehen, in denen Sie ein paar Prozent mehr Leistung erzielen können, wenn Sie die durch Warnungen wie diese erzielten Conversions reduzieren.

In einigen Fällen habe ich eine Kopfzeile mit strengen Warnungen, die ich behalte, um versehentliche Conversions zu verhindern. Dies ist jedoch ein Kompromiss, da Sie möglicherweise eine Menge Casts zu stillen absichtlichen Conversions hinzufügen, wodurch der Code möglicherweise nur minimaler wird gewinnt.

5
ideasman42

Unmöglich zu sagen. Es hängt davon ab, wie der Code aussieht. Wenn wir davon ausgehen können, dass der Code bereits vorhanden ist, können wir ihn einfach anschauen und daraus herausfinden, wie er optimiert werden kann.

Bessere Cache-Lokalität, Loop-Unrolling. Versuchen Sie, lange Abhängigkeitsketten zu eliminieren, um eine bessere Parallelität auf Befehlsebene zu erzielen. Ziehen Sie bedingte Bewegungen nach Möglichkeit den Zweigen vor. Nutzen Sie nach Möglichkeit die SIMD-Anweisungen.

Verstehen Sie, was Ihr Code tut, und verstehen Sie die Hardware, auf der er ausgeführt wird. Dann ist es ziemlich einfach zu bestimmen, was Sie tun müssen, um die Leistung Ihres Codes zu verbessern. Das ist wirklich der einzige wirklich allgemeine Rat, den ich mir vorstellen kann.

Nun, das und "Zeigen Sie den Code auf SO und fragen Sie nach Optimierungsempfehlungen für diesen bestimmten Code".

5
jalf

Hier sind einige schnelle und schmutzige Optimierungstechniken, die ich verwende. Ich halte dies für eine "First-Pass" -Optimierung.

Lerne, wo die Zeit verbracht wird Finde heraus, was genau die Zeit kostet. Ist es Datei IO? Ist es CPU-Zeit? Ist es das Netzwerk? Ist es die Datenbank? Es ist sinnlos, für IO zu optimieren, wenn das nicht der Engpass ist.

Know Your Environment In der Regel hängt es von der Entwicklungsumgebung ab, wo optimiert werden muss. In VB6 ist die Referenzübergabe beispielsweise langsamer als die Wertübergabe, in C und C++ ist die Referenzübergabe jedoch erheblich schneller. In C ist es sinnvoll, etwas zu versuchen und etwas anderes zu tun, wenn ein Rückkehrcode einen Fehler anzeigt, während in Dot Net das Abfangen von Ausnahmen viel langsamer ist, als vor dem Versuch auf eine gültige Bedingung zu prüfen.

Indizes Erstelle Indizes für häufig abgefragte Datenbankfelder. Sie können fast immer Platz gegen Geschwindigkeit eintauschen.

Nachschlagen vermeiden Innerhalb der zu optimierenden Schleife muss ich nicht nachschlagen. Suchen Sie den Offset und/oder Index außerhalb der Schleife und verwenden Sie die darin enthaltenen Daten erneut.

Minimize IO Versuchen Sie, das Design so zu gestalten, dass Sie weniger häufig lesen oder schreiben müssen, insbesondere über eine Netzwerkverbindung

Abstraktionen reduzieren Je mehr Abstraktionsebenen der Code durcharbeiten muss, desto langsamer ist er. Reduzieren Sie innerhalb der kritischen Schleife Abstraktionen (d. H. Decken Sie Methoden auf niedrigerer Ebene auf, die zusätzlichen Code vermeiden).

Spawn Threads Für Projekte mit einer Benutzeroberfläche führt das Öffnen eines neuen Threads zum Ausführen langsamerer Aufgaben dazu, dass sich die Anwendung reaktionsschneller anfühlt ist nicht.

Vorverarbeitung Im Allgemeinen können Sie Speicherplatz gegen Geschwindigkeit eintauschen. Wenn es Berechnungen oder andere intensive Operationen gibt, prüfen Sie, ob Sie einige der Informationen vorberechnen können, bevor Sie sich in der kritischen Schleife befinden.

5
Andrew Neely

Wenn bessere Hardware eine Option ist, dann entscheiden Sie sich definitiv dafür. Andernfalls

  • Überprüfen Sie, ob Sie die besten Compiler- und Linkeroptionen verwenden.
  • Wenn sich die Hotspot-Routine in einer anderen Bibliothek befindet als der häufig verwendete Anrufer, ziehen Sie in Betracht, sie in das Anrufermodul zu verschieben oder zu klonen. Beseitigt einen Teil des Aufrufaufwands und kann die Cachetreffer verbessern (siehe AIX verknüpft strcpy () statisch mit separat verknüpften gemeinsam genutzten Objekten). Dies könnte natürlich auch die Anzahl der Cachetreffer verringern, weshalb eine Maßnahme ergriffen wird.
  • Überprüfen Sie, ob eine spezielle Version der Hotspot-Routine verwendet werden kann. Nachteil ist, dass mehr als eine Version zu warten ist.
  • Schau dir den Assembler an. Wenn Sie glauben, es könnte besser sein, überlegen Sie, warum der Compiler dies nicht herausgefunden hat und wie Sie dem Compiler helfen können.
  • Überlegen Sie: Verwenden Sie wirklich den besten Algorithmus? Ist dies der beste Algorithmus für Ihre Eingabegröße?
5
mealnor

Der Google-Weg ist eine Option "Cache it .. Wenn immer möglich, berühren Sie nicht die Festplatte"

5
asyncwait

Variable Größen reduzieren (in eingebetteten Systemen)

Wenn Ihre Variable in einer bestimmten Architektur größer als Word ist, kann dies erhebliche Auswirkungen auf die Codegröße und -geschwindigkeit haben. Wenn Sie zum Beispiel ein 16-Bit-System haben und ein long int Variable sehr oft, und später erkennen, dass es nie außerhalb des Bereichs (-32.768 ... 32.767) kommen kann. Überlegen Sie, es auf short int.

Wenn ein Programm nach meiner persönlichen Erfahrung fertig oder fast fertig ist, wir jedoch feststellen, dass es etwa 110% oder 120% des Programmspeichers der Zielhardware beansprucht, löst eine schnelle Normalisierung von Variablen das Problem in der Regel häufiger als gewöhnlich.

Zu diesem Zeitpunkt kann es frustrierend sein, die Algorithmen oder Teile des Codes selbst zu optimieren:

  • reorganisieren Sie die gesamte Struktur und das Programm funktioniert nicht mehr wie vorgesehen, oder Sie führen zumindest eine Menge Fehler ein.
  • machen Sie einige clevere Tricks: Normalerweise verbringen Sie viel Zeit damit, etwas zu optimieren, und stellen fest, dass die Codegröße nicht oder nur geringfügig abnimmt, da der Compiler sie ohnehin optimiert hätte.

Viele Menschen machen den Fehler, Variablen zu haben, die genau den numerischen Wert einer Einheit speichern, für die sie die Variable verwenden: Beispielsweise speichert ihre Variable time die genaue Anzahl von Millisekunden, selbst wenn nur Zeitschritte von beispielsweise 50 ms vorliegen sind relevant. Wenn Ihre Variable 50 ms für jedes Inkrement von 1 darstellt, könnten Sie möglicherweise in eine Variable passen, die kleiner oder gleich der Wortgröße ist. Auf einem 8-Bit-System beispielsweise erzeugt bereits eine einfache Addition von zwei 32-Bit-Variablen eine angemessene Menge an Code, insbesondere wenn Sie nur wenige Register haben, während 8-Bit-Additionen sowohl klein als auch schnell sind.

4
vsz

Wenn Sie eine Menge hochparalleler Gleitkomma-Berechnungen haben, insbesondere mit einfacher Genauigkeit, sollten Sie diese mit OpenCL oder (für NVidia-Chips) CUDA auf einen Grafikprozessor (falls vorhanden) auslagern. GPUs haben eine immense Fließkomma-Rechenleistung in ihren Shadern, die viel höher ist als die einer CPU.

4
Demi

übergeben Sie als Verweis anstelle von Wert

Passen Sie das Betriebssystem und das Framework an.

Es mag übertrieben klingen, aber denken Sie darüber nach: Betriebssysteme und Frameworks sind für viele Dinge ausgelegt. Ihre Bewerbung macht nur ganz bestimmte Dinge. Wenn Sie das Betriebssystem dazu bringen könnten, genau das zu tun, was Ihre Anwendung benötigt, und Ihre Anwendung verstehen könnte, wie das Framework (php, .net, Java) funktioniert, könnten Sie Ihre Hardware viel besser nutzen.

Facebook hat zum Beispiel einige Kernel-Level-Dings in Linux geändert, die Funktionsweise von memcached geändert (zum Beispiel haben sie einen memcached-Proxy geschrieben und verwendet udp anstelle von tcp ).

Ein weiteres Beispiel hierfür ist Window2008. Win2K8 verfügt über eine Version, in der Sie nur das grundlegende Betriebssystem installieren können, das zum Ausführen von X-Anwendungen erforderlich ist (z. B. Web-Apps, Server-Apps). Dies reduziert den Overhead, den das Betriebssystem bei der Ausführung von Prozessen hat, und sorgt für eine bessere Leistung.

Natürlich sollten Sie als ersten Schritt immer mehr Hardware einwerfen ...

4
Nir Levy

Manchmal kann es hilfreich sein, das Layout Ihrer Daten zu ändern. In C können Sie von einem Array oder Strukturen zu einer Array-Struktur oder umgekehrt wechseln.

4
Nosredna

Eine solche pauschale Aussage ist nicht möglich, sie hängt von der Problemdomäne ab. Einige Möglichkeiten:

Da Sie nicht direkt angeben, dass Ihre Anwendung zu 100% berechnet:

  • Suchen Sie nach Aufrufen, die blockieren (Datenbank, Netzwerkfestplatte, Anzeigeaktualisierung), und isolieren Sie sie und/oder platzieren Sie sie in einem Thread.

Wenn Sie eine Datenbank verwendet haben und es sich zufällig um Microsoft SQL Server handelt:

  • untersuchen Sie Nolock- und Rowlock-Direktiven. (Es gibt Themen in diesem Forum.)

[~ # ~] Wenn [~ # ~] Ihre App nur berechnet, können Sie sich meine Frage zur Cache-Optimierung für das Drehen großer Bilder ansehen . Die Geschwindigkeitssteigerung hat mich verblüfft.

Es ist ein langer Versuch, aber vielleicht gibt es eine Idee, insbesondere wenn Ihr Problem im Bereich der Bildgebung liegt: rotierende Bitmaps im Code

Eine andere besteht darin, die dynamische Speicherzuweisung so weit wie möglich zu vermeiden. Ordnen Sie mehrere Strukturen gleichzeitig zu, und geben Sie sie gleichzeitig frei.

Identifizieren Sie andernfalls Ihre engsten Schleifen und veröffentlichen Sie sie hier, entweder in Pseudo- oder in Nicht-Pseudo-Form, mit einigen der Datenstrukturen.

3

In einer Sprache mit Vorlagen (C++/D) können Sie versuchen, konstante Werte über Vorlagenargumente zu verbreiten. Sie können dies sogar für kleine Mengen von nicht wirklich konstanten Werten mit einem Schalter tun.

Foo(i, j); // i always in 0-4.

wird

switch(i)
{
    case 0: Foo<0>(j); break;
    case 1: Foo<1>(j); break;
    case 2: Foo<2>(j); break;
    case 3: Foo<3>(j); break;
    case 4: Foo<4>(j); break;
}

Der Nachteil ist der Cache-Druck, sodass dies nur ein Gewinn bei tiefen oder lang laufenden Anrufbäumen ist, bei denen der Wert für die Dauer konstant ist.

3
BCS