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 ++i
oder 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.
Vorschläge:
Nachteile : Wenn nur wenige der vorberechneten Werte tatsächlich verwendet werden, kann dies die Sache verschlimmern, und auch die Suche kann erheblichen Speicherplatz beanspruchen.
Nachteile : Das Schreiben von zusätzlichem Code bedeutet mehr Oberfläche für Fehler.
Nachteile : Nun ... die Antwort wird nicht genau sein.
quelle
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:
Dadurch wird Ihr Programm nicht schneller, aber Ihre Benutzer werden möglicherweise mit der Geschwindigkeit, die Sie haben, zufriedener.
quelle
Ich verbringe den größten Teil meines Lebens an diesem Ort. Die Grundzüge sind, Ihren Profiler auszuführen und ihn aufzeichnen zu lassen:
__restrict
großzügig, um dem Compiler Aliasing zu versprechen.Und noch etwas, was ich gerne mache:
quelle
Examples on the PowerPC ...
<- Das heißt, einige Implementierungen von PowerPC. PowerPC ist eine ISA, keine CPU.lea
.Wirf mehr Hardware drauf!
quelle
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.
quelle
mmap()
für die Eingabe, führen Sie entsprechendemadvise()
Anrufe durch undaio_write()
schreiben Sie große Ausgabestücke (= einige MiB).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.
quelle
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:
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.
quelle
quelle
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.
quelle
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.
quelle
quelle
quelle
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.
quelle
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 :)
quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle
Die letzten paar% sind sehr CPU- und anwendungsabhängig ....
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 ...
quelle
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.
quelle
Nicht annähernd so ausführlich oder komplex wie die vorherigen Antworten, aber hier ist: (dies sind eher Anfänger / Fortgeschrittene)
quelle
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".
quelle
Wenn bessere Hardware eine Option ist, dann entscheiden Sie sich auf jeden Fall dafür. Andernfalls
quelle
Der Google-Weg ist eine Option "Cache it .. Wenn immer möglich, berühren Sie die Festplatte nicht"
quelle
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.
quelle
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.
quelle
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.
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.
quelle
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.
quelle
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 ...
quelle