Leistungsoptimierungsstrategien des letzten Auswegs [geschlossen]

609

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

Angenommen:

  • Der Code funktioniert bereits korrekt
  • Die gewählten Algorithmen sind bereits optimal für die Umstände des Problems
  • Der Code wurde gemessen und die fehlerhaften Routinen wurden isoliert
  • Alle Optimierungsversuche werden ebenfalls gemessen, um sicherzustellen, dass sie die Sache nicht verschlimmern

Was ich hier suche, sind Strategien und Tricks, um in einem kritischen Algorithmus bis zu den letzten Prozent herauszuholen, wenn nichts anderes zu tun ist, als was auch immer es braucht.

Versuchen Sie im Idealfall, die Antworten sprachunabhängig zu machen, und geben Sie gegebenenfalls Nachteile der vorgeschlagenen Strategien an.

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

jerryjvl
quelle

Antworten:

427

OK, Sie definieren das Problem so, dass es anscheinend nicht viel Raum für Verbesserungen gibt. Das ist meiner Erfahrung nach ziemlich selten. Ich habe versucht, dies in einem Artikel von Dr. Dobbs im November 1993 zu erklären, indem ich von einem konventionell gut gestalteten, nicht trivialen Programm ohne offensichtliche Verschwendung ausgegangen bin und es durch eine Reihe von Optimierungen geführt habe, bis die Wanduhrzeit von 48 Sekunden verkürzt wurde auf 1,1 Sekunden, und die Quellcode-Größe wurde um den Faktor 4 reduziert. Mein Diagnosetool war dies . Die Reihenfolge der Änderungen war folgende:

  • 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 ziemlich einfachen Code ersetzt, wodurch sich die Zeit auf 20 Sekunden verringerte.

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

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

Jetzt scheine ich gegen eine Wand gestoßen zu sein. 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 und seine transaktionsgesteuerte Struktur nach und frage, ob die gesamte Listensuche, die es durchführt, tatsächlich von den Anforderungen des Problems abhängt.

Dann stieß ich auf ein Re-Design, 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 herausfindet, was der Programmierer als ziemlich vorhersehbar kennt. Mit anderen Worten, "interpretieren" Sie die Abfolge der zu erledigenden Aufgaben nicht, "kompilieren" Sie sie.

  • Durch diese Neugestaltung wird der Quellcode um den Faktor 4 verkleinert und die Zeit auf 10 Sekunden reduziert.

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

  • Mehr Diagnose zeigt, dass es Zeit in der Warteschlangenverwaltung verbringt. Durch das Einkleiden wird die Zeit auf 7 Sekunden reduziert.

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

  • Jetzt sind die größten Zeitnehmer Anrufe zu malloc und frei . Objekte recyceln - 2,6 Sekunden.

  • Wenn ich weiter probiere, finde ich immer noch Operationen, die nicht unbedingt notwendig sind - 1,1 Sekunden.

Gesamtbeschleunigungsfaktor: 43,6

Jetzt sind keine zwei Programme gleich, aber in Nicht-Spielzeug-Software habe ich immer einen solchen Fortschritt gesehen. Zuerst bekommen Sie die einfachen Sachen und dann die schwierigeren, bis Sie zu einem Punkt kommen, an dem die Renditen sinken. Dann kann die gewonnene Erkenntnis durchaus zu einer Neugestaltung führen, die eine neue Runde von Beschleunigungen startet, bis Sie erneut auf sinkende Renditen stoßen. Nun ist dies der Punkt , an dem es Sinn , sich zu fragen , machen könnte , ob ++ioder i++oder for(;;)oder while(1)sind schneller: die Arten von Fragen , die ich sehen , so oft auf Stack - Überlauf.

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

Ich habe tatsächlich einen Profiler erstellt, um dies zu tun, aber für eine echte Intimität mit dem, was der Code tut, gibt es keinen Ersatz dafür, dass Sie Ihre Finger richtig darin haben. Es ist kein Problem, dass die Anzahl der Proben gering ist, da keines der gefundenen Probleme so klein ist, dass sie leicht übersehen werden.

HINZUGEFÜGT: jerryjvl hat einige Beispiele angefordert. Hier ist das erste Problem. Es besteht aus einer kleinen Anzahl separater Codezeilen, die zusammen mehr als 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 die Benutzer der Klasse sich nicht darum kümmern sollten, wie sie implementiert wurden. Als diese Zeilen geschrieben wurden (aus ungefähr 800 Codezeilen), wurde nicht daran gedacht, dass dies ein "Engpass" sein könnte (ich hasse dieses Wort). Sie sind einfach die empfohlene Art, Dinge zu tun. Im Nachhinein ist es leicht zu sagen, dass diese hätten vermieden werden müssen, aber meiner Erfahrung nach sind alle Leistungsprobleme so. Im Allgemeinen ist es gut zu versuchen, Leistungsprobleme zu vermeiden. Es ist sogar noch besser, diejenigen zu finden und zu reparieren, die erstellt wurden, obwohl sie (im Nachhinein) "hätten vermieden werden müssen".

Hier ist das zweite Problem in zwei getrennten 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 auf dem Aufrufstapel waren), sodass sie nicht vorhanden waren Tatsache, ein großes Problem am Anfang . Nachdem sie das erste Problem beseitigt hatten, kosteten sie 3/20 der Zeit und waren nun ein "größerer Fisch". Im Allgemeinen geht es so.

Ich könnte hinzufügen, dass dieses Projekt aus einem echten Projekt destilliert wurde, an dem ich mitgearbeitet habe. In diesem Projekt waren die Leistungsprobleme weitaus dramatischer (ebenso wie die Beschleunigungen), z. B. das Aufrufen einer Datenbankzugriffsroutine innerhalb einer inneren Schleife, um festzustellen, ob eine Aufgabe abgeschlossen wurde.

HINWEIS HINZUGEFÜGT: Der Quellcode, sowohl original als auch neu gestaltet, befindet sich unter www.ddj.com für 1993 in den Dateien 9311.zip, den Dateien slug.asc und slug.zip.

EDIT 26.11.2011: Es gibt jetzt ein SourceForge-Projekt, das Quellcode in Visual C ++ und eine ausführliche Beschreibung der Optimierung enthält. Es durchläuft nur die erste Hälfte des oben beschriebenen Szenarios und folgt nicht genau der gleichen Reihenfolge, erhält aber dennoch eine Beschleunigung um 2-3 Größenordnungen.

Mike Dunlavey
quelle
3
Ich würde gerne einige Details der Schritte lesen, die Sie oben skizzieren. Ist es möglich, einige Fragmente der Geschmacksoptimierungen aufzunehmen? (ohne den Beitrag zu lang zu machen?)
jerryjvl
8
... Ich habe auch ein Buch geschrieben, das jetzt vergriffen ist, also wird es bei Amazon zu einem lächerlichen Preis angeboten - "Building Better Applications" ISBN 0442017405. Im Wesentlichen das gleiche Material befindet sich im ersten Kapitel.
Mike Dunlavey
3
@ Mike Dunlavey, ich würde vorschlagen, Google mitzuteilen, dass Sie es bereits eingescannt haben. Sie haben wahrscheinlich bereits eine Vereinbarung mit demjenigen, der Ihren Verlag gekauft hat.
Thorbjørn Ravn Andersen
19
@ Thorbjørn: Nur um nachzufragen, habe ich mich mit GoogleBooks verbunden, alle Formulare ausgefüllt und ihnen eine Hardcopy geschickt. Ich erhielt eine E-Mail zurück und fragte, ob ich wirklich wirklich das Urheberrecht besitze. Der Verlag Van Nostrand Reinhold, der von International Thompson gekauft wurde, der von Reuters gekauft wurde, und wenn ich versuche, sie anzurufen oder per E-Mail zu kontaktieren, ist das wie ein Schwarzes Loch. Es ist also in der Schwebe - ich hatte noch nicht die Energie, es wirklich zu jagen.
Mike Dunlavey
5
Google Books Link: books.google.dk/books?id=8A43E1UFs_YC
Thorbjørn Ravn Andersen
188

Vorschläge:

  • Vorberechnung statt Neuberechnung : 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 alle Werte im gültigen Bereich von enthält Eingänge. Verwenden Sie stattdessen eine einfache Suche im Algorithmus.
    Nachteile : Wenn nur wenige der vorberechneten Werte tatsächlich verwendet werden, kann dies die Sache verschlimmern, und auch die Suche kann erheblichen Speicherplatz beanspruchen.
  • Verwenden Sie keine Bibliotheksmethoden : Die meisten Bibliotheken müssen so geschrieben werden, dass sie in einer Vielzahl von Szenarien ordnungsgemäß funktionieren und keine Parameter usw. überprüfen. Wenn Sie eine Methode erneut implementieren, können Sie möglicherweise eine Menge Logik entfernen gilt nicht unter den genauen Umständen, unter denen Sie es verwenden.
    Nachteile : Das Schreiben von zusätzlichem Code bedeutet mehr Oberfläche für Fehler.
  • Verwenden Sie Bibliotheksmethoden : Um mir selbst zu widersprechen, werden Sprachbibliotheken von Leuten geschrieben, die viel schlauer sind als Sie oder ich. Die Chancen stehen gut, dass sie es besser und schneller gemacht haben. Implementieren Sie es nicht selbst, es sei denn, Sie können es tatsächlich schneller machen (dh: immer messen!)
  • Cheat : In einigen Fällen, obwohl eine genaue Berechnung für Ihr Problem existiert, benötigen Sie möglicherweise nicht "genau", 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% ausfällt? 5%? sogar 10%?
    Nachteile : Nun ... die Antwort wird nicht genau sein.
jerryjvl
quelle
32
Vorberechnung hilft nicht immer und kann manchmal sogar weh tun. Wenn Ihre Nachschlagetabelle zu groß ist, kann dies die Cache-Leistung beeinträchtigen.
Adam Rosenfield
37
Betrug kann oft der Gewinn sein. Ich hatte einen Farbkorrekturprozess, bei dem es sich im Kern um einen 3-Vektor handelte, der mit einer 3x3-Matrix gepunktet war. Die CPU hatte eine Matrix-Multiplikation in der Hardware, die einige der Kreuzterme ausließ und im Vergleich zu allen anderen Methoden sehr schnell ging, aber nur 4x4-Matrizen und 4-Vektoren von Floats unterstützte. Das Ändern des Codes, um den zusätzlichen leeren Steckplatz zu transportieren, und das Konvertieren der Berechnung in Gleitkomma vom Festpunkt ermöglichten ein etwas weniger genaues, aber viel schnelleres Ergebnis.
RBerteig
6
Das Betrügen bestand in der Verwendung einer Matrixmultiplikation, bei der einige der inneren Produkte weggelassen wurden, wodurch es möglich wurde, einen einzelnen CPU-Befehl in Mikrocode zu implementieren, der schneller ausgeführt wurde, als es selbst die entsprechende Folge einzelner Befehle konnte. Es ist ein Betrug, weil es nicht die "richtige" Antwort bekommt, sondern nur eine Antwort, die "richtig genug" ist.
RBerteig
6
@RBerteig: Nur "richtig genug" ist eine Gelegenheit zur Optimierung, die die meisten Leute meiner Erfahrung nach vermissen.
Martin Thompson
5
Sie können nicht immer davon ausgehen, dass jeder intelligenter ist als Sie. Am Ende sind wir alle Profis. Sie können jedoch davon ausgehen, dass eine bestimmte Bibliothek, die Sie verwenden, vorhanden ist und aufgrund ihrer Qualität Ihre Umgebung erreicht hat. Daher muss das Schreiben dieser Bibliothek sehr gründlich sein. Sie können dies auch nicht tun, nur weil Sie nicht darauf spezialisiert sind Feld, und Sie investieren nicht die gleiche Zeit in es. Nicht weil du weniger schlau bist. Komm schon.
v.oddou
164

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

Möglicherweise können Sie Ihren fooCalc-Algorithmus nicht schneller machen, aber häufig gibt es Möglichkeiten, Ihre Anwendung für den Benutzer reaktionsfähiger erscheinen zu lassen.

Einige Beispiele:

  • Vorwegnehmen, was der Benutzer anfordern wird, und vorher damit beginnen
  • 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.

kenj0418
quelle
27
Ein am Ende beschleunigter Fortschrittsbalken kann als schneller als ein absolut genauer wahrgenommen werden. In "Rethinking the Progress Bar" (2007) testen Harrison, Amento, Kuznetsov und Bell mehrere Arten von Balken an einer Gruppe von Benutzern und diskutieren einige Möglichkeiten, die Vorgänge neu anzuordnen, damit der Fortschritt als schneller wahrgenommen werden kann.
Emil Vikström
9
Naxa, die meisten Fortschrittsbalken sind gefälscht, da es schwierig oder manchmal unmöglich ist, mehrere sehr unterschiedliche Schritte eines Flusses in einen einzelnen Prozentsatz vorherzusagen. Schauen Sie sich all die Bars an, die bei 99% hängen bleiben :-(
Emil Vikström
138

Ich verbringe den größten Teil meines Lebens an diesem Ort. Die Grundzüge sind, Ihren Profiler auszuführen und ihn aufzeichnen zu lassen:

  • Cache fehlt . Der Datencache ist in den meisten Programmen die häufigste Quelle für Verzögerungen. Verbessern Sie die Cache-Trefferquote, indem Sie fehlerhafte 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 eliminieren. Daten vorab abrufen, wo immer dies möglich ist, um Verzögerungen zu reduzieren.
  • Load-Hit-Stores . Compiler-Annahmen zum Zeiger-Aliasing und zu Fällen, in denen Daten zwischen getrennten Registersätzen über den Speicher verschoben werden, können ein bestimmtes pathologisches Verhalten verursachen, das dazu führt, dass die gesamte CPU-Pipeline bei einem Ladevorgang gelöscht wird. Finden Sie Orte, an denen Floats, Vektoren und Ints aufeinander geworfen werden, und eliminieren Sie sie. Verwenden Sie __restrictgroßzügig, um dem Compiler Aliasing zu versprechen.
  • Mikrocodierte Operationen . Die meisten Prozessoren haben einige Operationen, die nicht per Pipeline übertragen werden können, sondern stattdessen eine winzige Unterroutine ausführen, die im ROM gespeichert ist. Beispiele auf dem PowerPC sind Integer-Multiplikation, Division und Shift-by-Variable-Betrag. Das Problem ist, dass die gesamte Pipeline während der Ausführung dieser Operation zum Stillstand kommt. Versuchen Sie, die Verwendung dieser Vorgänge zu unterbinden oder sie zumindest in ihre Pipeline-Ops zu zerlegen, damit Sie den Vorteil eines superskalaren Versands für alles nutzen können, was der Rest Ihres Programms tut.
  • Branch falsche Vorhersagen . 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 Verzweigungshinweise, falls verfügbar, damit diese häufiger korrekt vorhergesagt werden können. Oder noch besser, ersetzen Sie Zweige nach Möglichkeit durch bedingte Bewegungen, insbesondere nach Gleitkommaoperationen, da ihre Pipe normalerweise tiefer ist und das Lesen der Bedingungsflags nach fcmp zu einem Stillstand führen kann.
  • Sequentielle Gleitkommaoperationen . Machen Sie diese SIMD.

Und noch etwas, was ich gerne mache:

  • Stellen Sie Ihren Compiler so ein, dass er Assembly-Listen ausgibt, 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 tun sollte"? Es besteht die Möglichkeit, dass Ihr tatsächlicher Compiler dies nicht tut. Ich habe gesehen, dass GCC wirklich WTF-Code ausgibt.
Crashworks
quelle
8
Ich benutze meistens Intel VTune und PIX. Keine Ahnung, ob sie sich an C # anpassen können, aber sobald Sie diese JIT-Abstraktionsschicht haben, sind die meisten dieser Optimierungen außerhalb Ihrer Reichweite, außer um die Cache-Lokalität zu verbessern und möglicherweise einige Verzweigungen zu vermeiden.
Crashworks
6
Trotzdem kann die Überprüfung der Post-JIT-Ausgabe helfen, herauszufinden, ob es Konstrukte gibt, die sich in der JIT-Phase einfach nicht gut optimieren lassen ... Untersuchungen können niemals schaden, selbst wenn sich eine Sackgasse herausstellt.
Jerryjvl
5
Ich denke, viele Leute, auch ich, würden sich für diese "wtf-Baugruppe" von gcc interessieren. Ihr klingt nach einem sehr interessanten Job :)
BlueRaja - Danny Pflughoeft
1
Examples on the PowerPC ...<- Das heißt, einige Implementierungen von PowerPC. PowerPC ist eine ISA, keine CPU.
Billy ONeal
1
@BillyONeal Selbst auf moderner x86-Hardware kann imul die Pipeline blockieren. Siehe "Referenzhandbuch zur Optimierung von Intel® 64- und IA-32-Architekturen" §13.3.2.3: "Die Ausführung des Integer-Multiplikationsbefehls dauert mehrere Zyklen. Sie werden so weitergeleitet, dass ein Integer-Multiplikationsbefehl und ein weiterer Befehl mit langer Latenz vorwärts Fortschritte machen können Ausführungsphase. Ganzzahlige Multiplikationsbefehle blockieren jedoch die Ausgabe anderer Ganzzyklusbefehle mit einem Zyklus aufgrund der Anforderung der Programmreihenfolge. " Aus diesem Grund ist es normalerweise besser, wortausgerichtete Arraygrößen und zu verwenden lea.
Crashworks
78

Wirf mehr Hardware drauf!

sisve
quelle
30
Mehr Hardware ist nicht immer eine Option, wenn Sie über Software verfügen, die voraussichtlich auf Hardware ausgeführt wird, die bereits im Einsatz ist.
Doug T.
76
Keine sehr hilfreiche Antwort für jemanden, der Consumer-Software herstellt: Der Kunde möchte nicht, dass Sie sagen: "Kaufen Sie einen schnelleren Computer." Vor allem, wenn Sie Software schreiben, die auf eine Videospielkonsole abzielt.
Crashworks
19
@ Crashworks oder ein eingebettetes System. Wenn die letzte Funktion endlich
verfügbar
71
Ich musste einmal ein Programm debuggen, das einen großen Speicherverlust aufwies - seine VM-Größe wuchs um etwa 1 MB pro Stunde. Ein Kollege scherzte, ich müsse nur mit konstanter Geschwindigkeit Speicher hinzufügen . :)
j_random_hacker
9
Mehr Hardware: Ah ja, die Lebensader des mittelmäßigen Entwicklers. Ich weiß nicht, wie oft ich gehört habe "Füge eine weitere Maschine hinzu und verdopple die Kapazität!"
Olof Forshell
58

Weitere Vorschläge:

  • Vermeiden Sie E / A : Alle E / A (Festplatte, Netzwerk, Ports usw.) sind immer viel langsamer als jeder Code, der Berechnungen durchführt. Entfernen Sie daher alle E / A, die Sie nicht unbedingt benötigen.

  • E / A im Voraus verschieben : Laden Sie alle Daten, die Sie für eine Berechnung benötigen, im Voraus, damit Sie keine wiederholten E / A-Wartezeiten im Kern eines kritischen Algorithmus haben (und möglicherweise als Ergebnis wiederholt werden) Festplatten-Suche, wenn das Laden aller Daten mit einem Schlag das Suchen vermeiden kann).

  • Verzögerungs-E / A : Schreiben Sie Ihre Ergebnisse erst auf, wenn die Berechnung abgeschlossen ist, speichern Sie sie in einer Datenstruktur und geben Sie sie am Ende, wenn die harte Arbeit erledigt ist, auf einmal aus.

  • Threaded I / O : Für diejenigen, die es wagen, kombinieren Sie 'I / O im Voraus' oder 'Delay I / O' mit der tatsächlichen Berechnung, indem Sie das Laden in einen parallelen Thread verschieben, damit Sie arbeiten können, während Sie mehr Daten laden Bei einer Berechnung der bereits vorhandenen Daten oder bei der Berechnung des nächsten Datenstapels können Sie gleichzeitig die Ergebnisse des letzten Stapels aufschreiben.

Peter Mortensen
quelle
3
Beachten Sie, dass "Verschieben der E / A in einen parallelen Thread" auf vielen Plattformen (z. B. Windows NT) als asynchrone E / A erfolgen sollte.
Billy ONeal
2
E / A ist in der Tat ein kritischer Punkt, da es langsam ist und große Latenzen aufweist und Sie mit diesem Rat schneller werden können, aber es ist immer noch grundlegend fehlerhaft: Die Punkte sind die Latenz (die ausgeblendet werden muss) und der Syscall-Overhead ( Dies muss reduziert werden, indem die Anzahl der E / A-Anrufe verringert wird . Der beste Rat ist: Verwenden Sie ihn mmap()für die Eingabe, führen Sie entsprechende madvise()Anrufe durch und aio_write()schreiben Sie große Ausgabestücke (= einige MiB).
cmaster - wieder herstellen Monica
1
Diese letzte Option ist insbesondere in Java recht einfach zu implementieren. Es gab RIESIGE Leistungssteigerungen für Anwendungen, die ich geschrieben habe. Ein weiterer wichtiger Punkt (mehr als das Verschieben von E / A im Voraus) ist die SEQUENTIAL- und Großblock-E / A. Viele kleine Lesevorgänge sind aufgrund der Suchzeit der Festplatte weitaus teurer als ein großer.
BobMcGee
Irgendwann habe ich betrogen, um E / A zu vermeiden, indem ich alle Dateien vor der Berechnung vorübergehend auf eine RAM-Disk verschoben und anschließend zurück verschoben habe. Dies ist schmutzig, kann jedoch in Situationen nützlich sein, in denen Sie die Logik für die E / A-Aufrufe nicht steuern.
MD
48

Da viele der Leistungsprobleme Datenbankprobleme betreffen, werde ich Ihnen einige spezifische Dinge geben, die Sie beim Optimieren von Abfragen und gespeicherten Prozeduren beachten sollten.

Vermeiden Sie Cursor in den meisten Datenbanken. Vermeiden Sie auch Schleifen. In den meisten Fällen sollte der Datenzugriff satzbasiert sein und nicht von Datensatz zu Datensatz verarbeitet werden. Dies beinhaltet, dass keine einzelne gespeicherte Datensatzprozedur wiederverwendet wird, 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 Verknüpfungen (einschließlich Verknüpfungen zu abgeleiteten Tabellen, sofern möglich) (Ich weiß, dass dies für Microsoft SQL Server gilt, testen Sie jedoch die Hinweise, wenn Sie ein anderes Backend verwenden).

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

Machen Sie die Abfrage sargable . Das heißt, vermeiden Sie Dinge, die es unmöglich machen, die Indizes zu verwenden, z. B. 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 Zeichenfolgendatentyp in einen Datumsdatentyp zu konvertieren und dann die Berechnung durchzuführen.

Stecken Sie niemals eine Schleife in einen Auslöser!

In den meisten Datenbanken kann überprüft werden, wie die Abfrageausführung durchgeführt wird. In Microsoft SQL Server wird dies als Ausführungsplan bezeichnet. Überprüfen Sie diese zuerst, um festzustellen, wo Problembereiche liegen.

Überlegen Sie, wie oft die Abfrage ausgeführt wird und wie lange die Ausführung dauert, um festzustellen, was optimiert werden muss. Manchmal können Sie durch eine geringfügige Änderung an einer Abfrage, die millionenfach am Tag ausgeführt wird, mehr Leistung erzielen, als wenn Sie die Zeit für eine long_running-Abfrage löschen, die nur einmal im Monat ausgeführt wird.

Verwenden Sie eine Art Profiler-Tool, um herauszufinden, was wirklich zur und von der Datenbank gesendet wird. Ich kann mich an ein Mal in der Vergangenheit erinnern, als wir nicht herausfinden konnten, warum das Laden der Seite so langsam war, als die gespeicherte Prozedur schnell war, und durch Profilerstellung herausfanden, dass die Webseite viele Male statt einmal nach der Abfrage fragte.

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

HLGEM
quelle
29

Der wichtigste einschränkende Faktor ist heute das begrenzte Speicherband . Multicores machen dies nur noch schlimmer, da die Bandbreite zwischen Kernen aufgeteilt wird. Außerdem wird der begrenzte Chipbereich, der für die Implementierung von Caches vorgesehen ist, auf die Kerne und Threads aufgeteilt, was dieses Problem noch weiter verschlimmert. Schließlich nimmt auch die Inter-Chip-Signalisierung, die erforderlich ist, um die verschiedenen Caches kohärent zu halten, mit zunehmender Anzahl von Kernen zu. Dies fügt auch eine Strafe hinzu.

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

In vielen Kommentaren wird bereits cachefreundlicher Code erwähnt. Es gibt mindestens zwei verschiedene Geschmacksrichtungen:

  • Vermeiden Sie Speicherabruflatenzen.
  • Niedrigerer Speicherbusdruck (Bandbreite).

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

Das zweite Problem hat mit der Verbesserung der Wiederverwendung von Daten zu tun. Ändern Sie Ihre Algorithmen so, dass sie Teilmengen Ihrer Daten bearbeiten, die in den verfügbaren Cache passen, und verwenden Sie diese Daten so oft wie möglich wieder, 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 nützlichere Daten in den Cache einfügen.

Matten N.
quelle
25
  • Auf welcher Hardware laufen Sie? Können Sie plattformspezifische Optimierungen (wie Vektorisierung) verwenden?
  • Können Sie einen besseren Compiler bekommen? ZB von GCC zu Intel wechseln?
  • Können Sie Ihren Algorithmus parallel laufen lassen?
  • Können Sie Cache-Fehler reduzieren, indem Sie Daten neu organisieren?
  • Können Sie Asserts deaktivieren?
  • Mikrooptimierung für Ihren Compiler und Ihre Plattform. Im Stil von "bei einem if / else die häufigste Aussage an erster Stelle setzen"
Johan Kotlinski
quelle
4
Sollte "von GCC zu LLVM wechseln" sein :)
Zifre
4
Können Sie Ihren Algorithmus parallel laufen lassen? - das
Gegenteil
4
Es stimmt, dass das Reduzieren der Anzahl von Threads eine ebenso gute Optimierung sein kann
Johan Kotlinski,
Betreff: Mikrooptimierung: Wenn Sie die ASM-Ausgabe des Compilers überprüfen, können Sie die Quelle häufig so anpassen, dass sie von Hand gehalten wird, um eine bessere ASM-Ausgabe zu erzielen. Siehe Warum ist dieser C ++ - Code schneller als meine handgeschriebene Assembly zum Testen der Collatz-Vermutung? Weitere Informationen zum Helfen oder Schlagen des Compilers auf modernem x86.
Peter Cordes
17

Obwohl ich die Antwort von Mike Dunlavey mag, ist sie in der Tat eine großartige Antwort mit unterstützendem Beispiel, aber ich denke, sie könnte sehr einfach so ausgedrückt werden:

Finden Sie zuerst heraus, was am meisten Zeit in Anspruch nimmt, und verstehen Sie, warum.

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

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

Schließlich kann ein wenig Versuch und Irrtum mit Leistungsdaten zu verschiedenen Methoden zur Implementierung derselben Lösung oder potenziell unterschiedlichen Algorithmen Erkenntnisse liefern, mit deren Hilfe Zeitverschwender und Zeitsparer identifiziert werden können.

HPH, asoudmove.

Bewegung
quelle
16

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 sich irgendwann mit der Verteilung Ihrer Anwendung auf verschiedene Computer und Netzwerke befassen müssen, damit sie idealerweise nahezu linear skaliert werden kann mit der Hardware, die Sie darauf werfen.

Auf der anderen Seite 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 die Optimierung des gesamten Programms für gcc durch ein engagiertes Team von Ingenieuren Hacken von gcc-Interna, um es auf Google-typische Anwendungsfallszenarien 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.

keine
quelle
15
  • Inline-Routinen (eliminieren Sie Call / Return und Parameter-Pushing)
  • Versuchen Sie, Tests / Schalter mit Tabellensuchen zu eliminieren (wenn sie schneller sind).
  • Rollen Sie die Schleifen (Duffs Gerät) bis zu dem Punkt ab, an dem sie gerade in den CPU-Cache passen
  • Lokalisieren Sie den Speicherzugriff, um Ihren Cache nicht zu sprengen
  • Lokalisieren Sie verwandte Berechnungen, wenn der Optimierer dies nicht bereits tut
  • Beseitigen Sie Schleifeninvarianten, wenn der Optimierer dies nicht bereits tut
Sockel
quelle
2
Das Gerät von IIRC Duff ist sehr selten schneller. Nur wenn die Operation sehr kurz ist (wie ein einzelner kleiner mathematischer Ausdruck)
BCS
12
  • Wenn Sie zu dem Punkt kommen, dass Sie effiziente Algorithmen verwenden, ist es eine Frage, was Sie mehr Geschwindigkeit oder Speicher benötigen . Verwenden Sie das Caching, um im Speicher für mehr Geschwindigkeit zu "bezahlen", oder verwenden Sie Berechnungen, um den Speicherbedarf zu verringern.
  • Wenn möglich (und kostengünstiger), werfen Sie Hardware auf das Problem - eine schnellere CPU, mehr Speicher oder HD könnten das Problem schneller lösen als der Versuch, es zu codieren.
  • Verwenden Sie nach Möglichkeit die Parallelisierung. 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, wobei verwalteter Code (dh Java / .NET) die Entwicklung beschleunigt, native Programmiersprachen jedoch schneller laufenden Code erzeugen.
  • Mikro optimieren . Nur wenn dies anwendbar ist, können Sie eine optimierte Assembly verwenden, um kleine Codeteile zu beschleunigen. Die Verwendung von SSE / Vektor-Optimierungen an den richtigen Stellen kann die Leistung erheblich steigern.
Dror Helfer
quelle
12

Teilen und erobern

Wenn das zu verarbeitende Dataset zu groß ist, durchlaufen Sie 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.

MPelletier
quelle
9
+1 für den Flyswatter "Smack" Sound, den ich beim Lesen des letzten Satzes gehört habe.
Bryan Boettcher
11

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

  • ... wenn es um Erinnerung geht - finden Sie eines der Bücher, die Knuth vor langer Zeit geschrieben hat, eines aus der Reihe "The Art of Computer Programming". Höchstwahrscheinlich geht es um Sortieren und Suchen - wenn mein Gedächtnis falsch ist, müssen Sie herausfinden, in welchem ​​er darüber spricht, wie man mit langsamer Banddatenspeicherung umgeht. Verwandeln Sie sein Speicher / Band- Paar mental in Ihr Paar Cache / Hauptspeicher (oder in ein Paar L1 / L2-Cache). Studieren Sie alle Tricks, die er beschreibt. Wenn Sie nichts finden, das Ihr Problem löst, beauftragen Sie einen professionellen Informatiker mit der Durchführung einer professionellen Forschung. Wenn Ihr Speicherproblem zufällig mit FFT zusammenhängt (Cache-Fehler bei bitumgekehrten Indizes bei Radix-2-Schmetterlingen), stellen Sie keinen Wissenschaftler ein. Optimieren Sie stattdessen die Pässe einzeln manuell, bis Sie entweder gewinnen oder in eine Sackgasse geraten. Du erwähntestbis zu den letzten paar Prozent auspressen, oder? Wenn es tatsächlich nur wenige sind, werden Sie höchstwahrscheinlich gewinnen.

  • ... wenn es Prozessor ist - wechseln Sie zur Assemblersprache. Prozessorspezifikation studieren - was Zecken braucht , VLIW, SIMD. Funktionsaufrufe sind höchstwahrscheinlich austauschbare Zeckenfresser. Lernen Sie Schleifentransformationen - Pipeline, Abrollen. Multiplikationen und Divisionen können durch Bitverschiebungen ersetzt / interpoliert werden (Multiplikationen mit kleinen ganzen Zahlen können durch Additionen ersetzt werden). Versuchen Sie Tricks mit kürzeren Daten - wenn Sie Glück haben, kann sich herausstellen, dass ein Befehl mit 64 Bit durch zwei auf 32 oder sogar 4 auf 16 oder 8 auf 8 Bit ersetzt werden kann. Versuchen Sie es auch längerDaten - z. B. können Ihre Float-Berechnungen bei einem bestimmten Prozessor langsamer ausfallen als doppelte. Wenn Sie trigonometrisches Material haben, bekämpfen Sie es mit vorberechneten Tabellen. Denken Sie auch daran, dass Sinus von geringem Wert 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 darüber übertragen. Ersetzen Sie die XML-Übertragung durch eine Binärdatei. Studienprotokolle. Versuchen Sie UDP anstelle von TCP, wenn Sie mit Datenverlust umgehen können.

  • ... wenn es sich um eine Datenbank handelt, gehen Sie zu einem Datenbankforum und fragen Sie um Rat. In-Memory-Datenraster, Optimierung des Abfrageplans usw. usw. usw.

HTH :)

Mücke
quelle
9

Caching! Eine kostengünstige Möglichkeit (in Programmieranstrengung), fast alles schneller zu machen, besteht darin, jedem Datenbewegungsbereich Ihres Programms eine Caching-Abstraktionsschicht hinzuzufügen. Sei es I / O oder nur das Übergeben / Erstellen von Objekten oder Strukturen. Oft ist es einfach, Factory-Klassen und Lesern / Schreibern Caches hinzuzufügen.

Manchmal bringt Ihnen der Cache nicht viel, aber es ist eine einfache Methode, einfach das gesamte Caching hinzuzufügen und es dann dort 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.

Killroy
quelle
8

Ich denke, das wurde schon anders gesagt. Wenn Sie jedoch mit einem prozessorintensiven Algorithmus arbeiten, sollten Sie alles in der innersten Schleife auf Kosten aller 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 beispielsweise mit verschachtelten Schleifen arbeiten und die Möglichkeit finden, Code auf einer Ebene zu reduzieren, können Sie Ihren Code in einigen Fällen drastisch beschleunigen. Als weiteres Beispiel gibt es kleine Dinge, über die Sie nachdenken sollten, wie die Arbeit mit ganzen Zahlen anstelle von Gleitkommavariablen, wann immer Sie können, und die Verwendung der Multiplikation anstelle der Division, wann immer Sie können. Auch dies sind Dinge, die für Ihre innerste Schleife berücksichtigt werden sollten.

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

Steve Wortham
quelle
8

Ich habe einige Zeit damit verbracht, Client / Server-Geschäftssysteme zu optimieren, die über Netzwerke mit geringer Bandbreite und langer Latenz (z. B. Satellit, Remote, Offshore) betrieben werden, und konnte mit einem ziemlich wiederholbaren Prozess einige dramatische Leistungsverbesserungen erzielen.

  • Maßnahme : 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 Client-Standort während typischer Betriebsperioden zu ermitteln. Nehmen Sie als Nächstes genaue Zeitmessungen bestimmter Endbenutzerfunktionen vor, die die problematischen Symptome anzeigen. Notieren Sie alle diese Messungen zusammen mit ihren Orten, Daten und Zeiten. Erwägen Sie, Endbenutzerfunktionen zum Testen der Netzwerkleistung in Ihre Clientanwendung zu integrieren, damit Ihre Hauptbenutzer am Verbesserungsprozess teilnehmen können. Eine solche Befähigung kann enorme psychologische Auswirkungen haben, wenn Sie mit Benutzern zu tun haben, die von einem System mit schlechter Leistung frustriert sind.

  • Analysieren : Verwenden Sie alle verfügbaren Protokollierungsmethoden, um genau festzustellen, welche Daten während der Ausführung der betroffenen Vorgänge gesendet und empfangen werden. Im Idealfall kann Ihre Anwendung Daten erfassen, die sowohl vom Client als auch vom Server gesendet und empfangen werden. Wenn diese auch Zeitstempel enthalten, noch besser. Wenn keine ausreichende 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 ziehen Sie eine geeignete Caching-Strategie in Betracht. Typische Beispiele sind "Auswahllisten" -Werte oder andere "Referenzentitäten", 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, insbesondere wenn die Anzeige häufig verwendeter Benutzeroberflächenelemente erheblich Zeit spart. Stellen Sie sicher, dass Sie das tatsächliche Verhalten der bereits bereitgestellten Caching-Elemente verstehen. Viele gängige Caching-Methoden (z. B. HTTP ETag) erfordern immer noch einen Netzwerk-Roundtrip, um die Konsistenz sicherzustellen. Wenn die Netzwerklatenz 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 Sekunden aufwies, was für eine einzelne Transaktion kein Problem darstellte, aber 6 aufeinanderfolgende 2-Sekunden-Roundtrips erforderlich waren, bevor der Benutzer die Kontrolle über die Clientanwendung wiedererlangte Es wurde eine große Quelle der Frustration. Durch die Feststellung, dass diese Transaktionen tatsächlich unabhängig waren, konnten sie parallel ausgeführt werden, wodurch sich die Verzögerung des Endbenutzers auf sehr nahe an den Kosten einer einzelnen Hin- und Rückfahrt verringerte.

  • Kombinieren : Wo sequentielle Anfragen müssen nacheinander ausgeführt werden, suchen Möglichkeiten sie zu einer einzigen umfassenden Anfrage zu kombinieren. Typische Beispiele sind die Erstellung neuer Entitäten, gefolgt von Anforderungen, diese Entitäten mit anderen vorhandenen Entitäten in Beziehung zu setzen.

  • Komprimieren : Suchen Sie nach Möglichkeiten, die Komprimierung der Nutzdaten zu nutzen, indem Sie entweder ein Textformular durch ein binäres Formular ersetzen oder die eigentliche Komprimierungstechnologie verwenden. Viele moderne (dh innerhalb eines Jahrzehnts) Technologie-Stacks unterstützen dies fast transparent. Stellen Sie daher sicher, dass sie konfiguriert sind. Ich war oft überrascht über die erheblichen Auswirkungen der Komprimierung, bei der es offensichtlich war, dass das Problem im Wesentlichen eher die Latenz als die Bandbreite war. Dabei stellte ich fest, dass die Transaktion in ein einzelnes Paket passt oder auf andere Weise Paketverluste vermeidet und daher eine Übergröße aufweist Auswirkungen auf die Leistung.

  • Wiederholen : Gehen Sie zurück zum Anfang und messen Sie Ihre Vorgänge (an denselben Orten und zu denselben Zeiten) mit den vorhandenen Verbesserungen neu, zeichnen Sie Ihre Ergebnisse auf und melden Sie sie. Wie bei jeder Optimierung wurden möglicherweise einige Probleme gelöst, um andere aufzudecken, die jetzt dominieren.

In den obigen Schritten konzentriere ich mich auf den anwendungsbezogenen Optimierungsprozess, aber natürlich müssen Sie 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. Normalerweise verstehen sie die Anforderungen Ihrer Anwendung nicht. Daher ist es wichtig, dass Sie (nach dem Analyseschritt) in der Lage sind, diese mit ihnen zu besprechen und das Geschäftsmodell für alle Kosten zu erstellen, die ihnen entstehen sollen . Ich bin auf Fälle gestoßen, in denen eine fehlerhafte Netzwerkkonfiguration dazu führte, dass die Anwendungsdaten über eine langsame Satellitenverbindung und nicht über eine Überlandverbindung übertragen wurden. einfach, weil ein TCP-Port verwendet wurde, der den Netzwerkspezialisten nicht "bekannt" war; Die Behebung eines solchen Problems kann sich offensichtlich dramatisch auf die Leistung auswirken, ohne dass Software-Code oder Konfigurationsänderungen erforderlich sind.

Klopfen
quelle
7

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 den Assembler-Code von Hand.

dschwarz
quelle
7

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

  • Cache-Architekturen unterscheiden sich, einige Chips haben On-Chip-RAM, den Sie direkt zuordnen können, ARMs haben (manchmal) eine Vektoreinheit, SH4 ist ein nützlicher Matrix-Opcode. Gibt es eine GPU - vielleicht ist ein Shader der richtige Weg. TMS320 reagieren sehr empfindlich auf Verzweigungen innerhalb von Schleifen (trennen Sie daher Schleifen und verschieben Sie die Bedingungen nach Möglichkeit nach draußen).

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 anhand des Codes aus. Oder das CCStudio von Texas Instruments hat einen süßen Profiler. Dann wissen Sie wirklich, wo Sie sich konzentrieren müssen ...

Peter Mortensen
quelle
7

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

Bei nicht-Offline-Projekten mit bester Software und bester Hardware wird diese dünne Linie bei schwachem Durchsatz Daten komprimieren und zu Verzögerungen führen, wenn auch in Millisekunden ... aber wenn Sie über die letzten Tropfen sprechen Das sind ein paar Tropfen, die rund um die Uhr für jedes gesendete oder empfangene Paket gewonnen wurden.

Sam
quelle
7

Nicht annähernd so ausführlich oder komplex wie die vorherigen Antworten, aber hier ist: (dies sind eher Anfänger / Fortgeschrittene)

  • 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 auf
  • Objekte zwischenspeichern
  • Lokale Variablen haben einen leichten Leistungsvorteil
  • Begrenzen Sie die Manipulation von Zeichenfolgen so weit wie möglich
Aaron
quelle
4
Informationen zum Rückwärtsschleifen: Ja, der Vergleich für das Schleifenende ist schneller. In der Regel verwenden Sie die Variable jedoch zum Indizieren in den Speicher, und der umgekehrte Zugriff darauf kann aufgrund häufiger Cache-Fehler (kein Prefetch) kontraproduktiv sein.
Andreas Reiff
1
AFAIK, in den meisten Fällen kann jeder vernünftige Optimierer mit Schleifen gut umgehen, ohne dass der Programmierer explizit umgekehrt laufen muss. Entweder kehrt der Optimierer die Schleife selbst um oder es gibt einen anderen Weg, der gleich gut ist. Ich habe eine identische ASM-Ausgabe für (zugegebenermaßen relativ einfache) Schleifen festgestellt, die sowohl aufsteigend als auch maximal und absteigend gegen 0 geschrieben wurden. Sicher, meine Z80-Tage haben die Angewohnheit, reflexartig Rückwärtsschleifen zu schreiben, aber ich vermute, dass es für Neulinge normalerweise eine ist Red Herring / vorzeitige Optimierung, wenn lesbarer Code und das Erlernen wichtigerer Praktiken Priorität haben sollten.
underscore_d
Im Gegensatz dazu ist das Rückwärtslaufen einer Schleife in Sprachen niedrigerer Ebenen langsamer, da in einem Krieg zwischen dem Vergleich mit Null plus zusätzlicher Subtraktion gegenüber einem Vergleich mit einer einzelnen Ganzzahl der Vergleich mit einer einzelnen Ganzzahl schneller ist. Anstatt zu dekrementieren, können Sie einen Zeiger auf die Startadresse im Speicher und einen Zeiger auf die Endadresse im Speicher haben. Erhöhen Sie dann den Startzeiger, bis er dem Endzeiger entspricht. Dies eliminiert die zusätzliche Speicheroffset-Operation im Assembler-Code und erweist sich somit als viel leistungsfähiger.
Jack Giffin
5

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 uns einfach ansehen und daraus herausfinden, wie wir ihn optimieren können.

Bessere Cache-Lokalität, Abrollen der Schleife. Versuchen Sie, lange Abhängigkeitsketten zu eliminieren, um eine bessere Parallelität auf Befehlsebene zu erzielen. Bevorzugen Sie nach Möglichkeit bedingte Bewegungen über Zweige. 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 wird 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 Optimierungshinweisen für diesen bestimmten Code".

jalf
quelle
5

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

  • Überprüfen Sie, ob Sie die besten Compiler- und Linker-Optionen verwenden.
  • Wenn die Hotspot-Routine in einer anderen Bibliothek häufig aufgerufen wird, sollten Sie sie in das Anrufermodul verschieben oder klonen. Beseitigt einen Teil des Anrufaufwands und kann die Cache-Treffer verbessern (vgl. Wie AIX strcpy () statisch zu separat verknüpften gemeinsam genutzten Objekten verknüpft). Dies könnte natürlich auch die Cache-Treffer verringern, weshalb eine Maßnahme getroffen wird.
  • Überprüfen Sie, ob die Verwendung einer speziellen Version der Hotspot-Routine möglich ist. Nachteil ist, dass mehr als eine Version gewartet werden muss.
  • Schauen Sie sich den Assembler an. Wenn Sie der Meinung sind, dass es besser sein könnte, überlegen Sie, warum der Compiler dies nicht herausgefunden hat und wie Sie dem Compiler helfen können.
  • Bedenken Sie: Verwenden Sie wirklich den besten Algorithmus? Ist es der beste Algorithmus für Ihre Eingabegröße?
Mealnor
quelle
Ich möchte zu Ihrem ersten Absatz hinzufügen: Vergessen Sie nicht, alle Debugging-Informationen in Ihren Compiler-Optionen zu deaktivieren .
Varnie
5

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

asyncwait
quelle
5

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

Erfahren Sie, wo die Zeit verbracht wird Finden Sie heraus, was genau kostet. Ist es Datei IO? Ist es CPU-Zeit? Ist es das Netzwerk? Ist es die Datenbank? Es ist sinnlos, für E / A zu optimieren, wenn dies nicht der Engpass ist.

Kennen Sie Ihre Umgebung Wissen, wo optimiert werden muss, hängt normalerweise von der Entwicklungsumgebung ab. In VB6 beispielsweise ist das Übergeben als Referenz langsamer als das Übergeben als Wert, in C und C ++ ist das Übergeben als Referenz jedoch erheblich schneller. In C ist es sinnvoll, etwas zu versuchen und etwas anderes zu tun, wenn ein Rückkehrcode auf einen Fehler hinweist. In Dot Net ist das Abfangen von Ausnahmen viel langsamer als das Überprüfen auf eine gültige Bedingung vor dem Versuch.

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

Vermeiden Sie Suchvorgänge Innerhalb der zu optimierenden Schleife muss ich keine Suchvorgänge durchführen. Suchen Sie den Offset und / oder Index außerhalb der Schleife und verwenden Sie die Daten innerhalb der Schleife wieder.

IO minimieren Versuche so zu gestalten, dass weniger gelesen oder geschrieben werden muss, insbesondere über eine Netzwerkverbindung

Abstraktionen reduzieren Je mehr Abstraktionsebenen der Code durchlaufen muss, desto langsamer ist er. Reduzieren Sie innerhalb der kritischen Schleife Abstraktionen (z. B. offenbaren Sie Methoden auf niedrigerer Ebene, die zusätzlichen Code vermeiden).

Spawn-Threads für Projekte mit einer Benutzeroberfläche. Wenn Sie einen neuen Thread erstellen, um langsamere Aufgaben auszuführen, wird die Anwendung ausgeführt Gefühl mehr ansprechbar, obwohl nicht.

Vorverarbeitung Sie können im Allgemeinen 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.

Andrew Neely
quelle
5

Wenn Sie viele hochparallele Gleitkomma-Berechnungen haben, insbesondere mit einfacher Genauigkeit, versuchen Sie, diese mithilfe von OpenCL oder (für NVidia-Chips) CUDA auf einen Grafikprozessor (falls vorhanden) zu verlagern. GPUs verfügen über eine immense Gleitkomma-Rechenleistung in ihren Shadern, die viel größer ist als die einer CPU.

Demi
quelle
5

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

Minimieren Sie die implizite Konvertierung zwischen Typ und Vorzeichen:

Dies gilt zumindest für C / C ++, auch wenn Sie bereits denken Sie frei von Konvertierungen sind - manchmal ist es gut, das Hinzufügen von Compiler-Warnungen für Funktionen zu testen, 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 schneller werden können, indem Sie die durch Warnungen wie diese hervorgerufenen Conversions reduzieren.

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

ideasman42
quelle
Aus diesem Grund gefällt mir, dass in OCaml das Umwandeln zwischen numerischen Typen xplicit sein muss.
Gaius
@Gaius fair point - aber in vielen Fällen ist ein Sprachwechsel keine realistische Wahl. Da C / C ++ so weit verbreitet ist, ist es nützlich, sie strenger zu gestalten, selbst wenn es compilerspezifisch ist.
ideasman42
4

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

Nosredna
quelle
4

Optimieren Sie das Betriebssystem und das Framework.

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

Facebook hat zum Beispiel einige Dinge auf Kernel-Ebene unter Linux geändert, die Funktionsweise von Memcached geändert (zum Beispiel haben sie einen Memcached-Proxy geschrieben und udp anstelle von tcp verwendet ).

Ein weiteres Beispiel hierfür ist Window2008. Win2K8 hat eine Version, in der Sie nur das Basisbetriebssystem installieren können, das zum Ausführen von X-Anwendungen erforderlich ist (z. B. Web-Apps, Server-Apps). Dies reduziert einen Großteil des Overheads, den das Betriebssystem beim Ausführen von Prozessen hat, und bietet Ihnen eine bessere Leistung.

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

Nir Levy
quelle
2
Dies wäre ein gültiger Ansatz, nachdem alle anderen Ansätze fehlgeschlagen sind oder wenn ein bestimmtes Betriebssystem oder Framework-Feature für eine deutlich verringerte Leistung verantwortlich ist, aber das erforderliche Maß an Fachwissen und Kontrolle, um dies zu erreichen, ist möglicherweise nicht für jedes Projekt verfügbar.
Andrew Neely