636 Stimmen

Strategien zur Leistungsoptimierung als letztes Mittel

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

Nehmen wir an:

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

Was ich hier suche, sind Strategien und Tricks, um in einem kritischen Algorithmus bis zu den letzten paar Prozent herauszuholen, wenn nichts anderes übrig bleibt, als alles zu tun.

Versuchen Sie, die Antworten möglichst sprachunabhängig zu gestalten, und geben Sie gegebenenfalls die Nachteile der vorgeschlagenen Strategien an.

Ich werde eine Antwort mit meinen eigenen ersten Vorschlägen hinzufügen und freue mich auf alles, was der Stack Overflow-Community sonst noch einfällt.

59voto

jerryjvl Punkte 18807

Weitere Vorschläge:

  • E/A vermeiden : Jede E/A (Festplatte, Netzwerk, Ports usw.) ist wird immer viel langsamer sein als jeder Code, der Berechnungen durchführt, also beseitigen Sie alle E/A, die Sie nicht unbedingt brauchen.

  • E/A im Voraus verschieben : Laden Sie alle Daten, die Sie für eine Daten, die Sie für eine Berechnung benötigen, im Voraus, so dass Sie nicht wiederholte E/A-Wartezeiten innerhalb des Kerns eines kritischen Algorithmus (und möglicherweise als Folge davon wiederholte Festplattenzugriffe, wenn das Laden aller Daten auf einen Schlag das Suchen vermeiden kann).

  • Verzögerung I/O : Schreiben Sie Ihre Ergebnisse erst am Ende der Berechnung beendet ist, speichern Sie sie in einer Datenstruktur und speichern Sie sie in einer Datenstruktur und geben Sie diese am Ende, wenn die harte Arbeit erledigt ist.

  • E/A mit Gewinde : Wer mutig genug ist, kombiniert 'I/O Up-front" oder "Delay I/O" mit der eigentlichen Berechnung, indem das Laden in einen parallelen Thread verschieben, so dass Sie, während Sie mehr Daten laden, können Sie an einer Berechnung im den bereits vorhandenen Daten arbeiten können, oder während Sie den nächsten Stapel von Daten berechnen, können Sie gleichzeitig die Ergebnisse des letzten des letzten Stapels ausgeben.

3 Stimmen

Beachten Sie, dass das "Verschieben der IO in einen parallelen Thread" auf vielen Plattformen (z. B. Windows NT) als asynchrone IO durchgeführt werden sollte.

2 Stimmen

E/A ist in der Tat ein kritischer Punkt, denn sie ist langsam und hat enorme Latenzzeiten, und man kann mit diesem Ratschlag schneller werden, aber er ist immer noch grundlegend fehlerhaft: Die Punkte sind die Latenz (die versteckt werden muss) und der Syscall-Overhead (der reduziert werden muss, indem man die Nummer von E/A-Aufrufen). Der beste Rat ist: verwenden Sie mmap() für die Eingabe, entsprechend tun madvise() Anrufe und Verwendung aio_write() um große Teile der Ausgabe (= einige MiB) zu schreiben.

1 Stimmen

Diese letzte Option ist vor allem in Java recht einfach zu implementieren. Sie hat bei Anwendungen, die ich geschrieben habe, zu enormen Leistungssteigerungen geführt. Ein weiterer wichtiger Punkt (mehr als die Verlagerung von E/A im Vorfeld) ist die SEQUENTIELLE und blockweise E/A. Viele kleine Lesevorgänge sind aufgrund der Suchzeit auf der Festplatte viel teurer als ein großer.

48voto

HLGEM Punkte 91543

Da viele der Leistungsprobleme mit Datenbankproblemen zu tun haben, gebe ich Ihnen einige spezifische Dinge an die Hand, die Sie beim Tuning von Abfragen und gespeicherten Prozeduren beachten sollten.

Vermeiden Sie Cursors in den meisten Datenbanken. Vermeiden Sie auch Schleifen. Der Datenzugriff sollte in den meisten Fällen satzbasiert erfolgen und nicht Datensatz für Datensatz verarbeitet werden. Dazu gehört auch, dass Sie eine gespeicherte Prozedur mit einem einzigen Datensatz nicht wiederverwenden, wenn Sie 1.000.000 Datensätze auf einmal einfügen möchten.

Verwenden Sie niemals select *, sondern geben Sie nur die Felder zurück, die Sie tatsächlich benötigen. Dies gilt vor allem bei Joins, da die Join-Felder wiederholt werden und somit sowohl den Server als auch das Netzwerk unnötig belasten.

Vermeiden Sie die Verwendung von korrelierten Unterabfragen. Verwenden Sie Joins (einschließlich Joins zu abgeleiteten Tabellen, wo dies möglich ist) (ich weiß, dass dies für Microsoft SQL Server gilt, aber testen Sie den Rat, wenn Sie ein anderes Backend verwenden).

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

Machen Sie die Abfrage sargbar . Vermeiden Sie also Dinge, die die Verwendung der Indizes unmöglich machen, wie die Verwendung eines Platzhalters im ersten Zeichen einer like-Klausel oder einer Funktion in der Verknüpfung oder als linker Teil einer where-Anweisung.

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

Setzen Sie niemals eine Schlaufe in einen Abzug!

Die meisten Datenbanken verfügen über eine Möglichkeit, zu überprüfen, wie die Abfrage ausgeführt werden soll. In Microsoft SQL Server wird dies als Ausführungsplan bezeichnet. Prüfen Sie diesen zuerst, um festzustellen, wo die Probleme liegen.

Berücksichtigen Sie, wie oft die Abfrage ausgeführt wird und wie lange sie dauert, wenn Sie bestimmen, was optimiert werden muss. Manchmal können Sie durch eine geringfügige Optimierung einer Abfrage, die täglich millionenfach ausgeführt wird, mehr Leistung erzielen, als wenn Sie einer lang laufenden Abfrage, die nur einmal im Monat ausgeführt wird, Zeit abnehmen.

Verwenden Sie eine Art Profiler-Tool, um herauszufinden, was wirklich an die und von der Datenbank gesendet wird. Ich kann mich an ein Beispiel aus der Vergangenheit erinnern, bei dem wir nicht herausfinden konnten, warum die Seite so langsam geladen wurde, obwohl die gespeicherte Prozedur schnell war, und durch die Profilerstellung herausfanden, dass die Webseite die Abfrage viele Male statt einmal abfragte.

Mit dem Profiler können Sie auch feststellen, wer wen blockiert. Einige Abfragen, die allein schnell ausgeführt werden, können aufgrund von Sperren durch andere Abfragen sehr langsam werden.

30voto

Mats N Punkte 1271

Der wichtigste einschränkende Faktor ist heute die begrenzte Speicherkapazität . Multicores verschlimmern dies nur, da die Bandbreite zwischen den Kernen geteilt wird. Außerdem wird die begrenzte Chipfläche, die für die Implementierung von Caches zur Verfügung steht, unter den Kernen und Threads aufgeteilt, was dieses Problem noch verschlimmert. Schließlich steigt mit der Anzahl der Kerne auch der Signalisierungsaufwand zwischen den Chips, um die verschiedenen Caches kohärent zu halten. Dies stellt ebenfalls einen Nachteil dar.

Das sind die Auswirkungen, die Sie in den Griff bekommen müssen. Manchmal durch Mikromanagement des Codes, manchmal aber auch durch sorgfältige Überlegung und Refaktorierung.

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

  • Vermeiden Sie Latenzen beim Speicherabruf.
  • Geringerer Druck auf den Speicherbus (Bandbreite).

Das erste Problem hat vor allem damit zu tun, dass Sie Ihre Datenzugriffsmuster regelmäßiger gestalten, damit der Hardware Prefetcher effizient arbeiten kann. Vermeiden Sie eine dynamische Speicherzuweisung, 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 mit Teilmengen Ihrer Daten arbeiten, die in den verfügbaren Cache passen, und verwenden Sie diese Daten so oft wie möglich wieder, solange sie sich noch im Cache befinden.

Wenn Sie die Daten dichter packen und sicherstellen, dass Sie alle Daten in den Cache-Zeilen in den Hot-Loops verwenden, können Sie diese anderen Effekte vermeiden und mehr nützlich Daten im Cache.

26voto

Johan Kotlinski Punkte 24241
  • Mit welcher Hardware arbeiten Sie? Können Sie plattformspezifische Optimierungen (wie Vektorisierung) verwenden?
  • Können Sie einen besseren Compiler finden? Z.B. von GCC zu Intel wechseln?
  • Können Sie Ihren Algorithmus parallel laufen lassen?
  • Können Sie Cache-Misses durch Reorganisation von Daten reduzieren?
  • Können Sie Asserts deaktivieren?
  • Mikro-Optimierung für Ihren Compiler und Ihre Plattform. Nach dem Motto "bei einer if/else-Anweisung die häufigste Anweisung zuerst setzen"

4 Stimmen

Sollte "Wechsel von GCC zu LLVM" sein :)

4 Stimmen

Können Sie Ihren Algorithmus parallel laufen lassen? -- das Umgekehrte gilt auch

4 Stimmen

Allerdings kann die Reduzierung der Anzahl von Threads eine ebenso gute Optimierung sein.

17voto

asoundmove Punkte 1242

Obwohl mir die Antwort von Mike Dunlavey gefällt - es ist in der Tat eine großartige Antwort mit unterstützenden Beispielen -, denke ich, dass sie sehr einfach so ausgedrückt werden könnte:

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

Der Prozess der Identifizierung der Zeitfresser hilft Ihnen 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 sein sollte. Außerdem setzen wir voraus, dass Sie in Ihrem Streben nach Geschwindigkeit architekturunabhängig sein wollen.

Der Algorithmus mag zwar optimiert sein, aber die Implementierung ist es möglicherweise nicht. Anhand der Kennzeichnung können Sie erkennen, welcher Teil der richtige ist: Algorithmus oder Implementierung. Der Teil, der die meiste Zeit in Anspruch nimmt, ist also der Hauptkandidat für die Überprüfung. Da Sie aber sagen, dass Sie die letzten paar Prozent herausquetschen wollen, sollten Sie vielleicht auch die weniger wichtigen Teile untersuchen, die Teile, die Sie zunächst nicht so genau untersucht haben.

Schließlich kann ein bisschen Ausprobieren mit Leistungszahlen zu verschiedenen Arten der Implementierung derselben Lösung oder möglicherweise verschiedener Algorithmen zu Erkenntnissen führen, die helfen, Zeitfresser und Zeitsparer zu identifizieren.

HPH, asoudmove.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X