Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Go
Code

Machen Sie Ihre Go-Programme mit Profiling blitzschnell

by
Difficulty:IntermediateLength:LongLanguages:

German (Deutsch) translation by Tatsiana Bochkareva (you can also view the original English article)

Go wird häufig zum Schreiben verteilter Systeme, erweiterter Datenspeicher und Microservices verwendet. Die Leistung ist in diesen Bereichen der Schlüssel.

In diesem Tutorial erfahren Sie, wie Sie Ihre Programme profilieren, um sie blitzschnell (CPU besser nutzen) oder federleicht (weniger Speicher verwenden) zu machen. Ich werde mich mit CPU- und Speicherprofilen befassen, indem ich den pprof (den Go-Profiler) verwende, die Profile visualisiere und sogar Flammengraphen.

Bei der Profilerstellung wird die Leistung Ihres Programms in verschiedenen Dimensionen gemessen. Go bietet eine hervorragende Unterstützung für die Profilerstellung und kann die folgenden Abmessungen sofort profilieren:

  • eine Abtastung der CPU-Zeit pro Funktion UND Befehl
  • eine Stichprobe aller Heap-Zuordnungen
  • Stapelspuren aller aktuellen Goroutinen
  • Stack-Traces, die zur Erstellung neuer Betriebssystem-Threads führen
  • Stapelspuren, die zum Blockieren von Synchronisationsprimitiven führten
  • Stapeln Sie Spuren von Inhabern umstrittener Mutexe

Sie können sogar benutzerdefinierte Profile erstellen, wenn Sie möchten. Bei der Go-Profilerstellung wird eine Profildatei erstellt und anschließend mit dem Tool pprof go analysiert.

Wie Sie Profildateien erstellen

Es gibt verschiedene Möglichkeiten, eine Profildatei zu erstellen.

Verwenden von "go test" zum Generieren von Profildateien

Am einfachsten ist es, den go test zu verwenden. Es verfügt über mehrere Flags, mit denen Sie Profildateien erstellen können. So generieren Sie sowohl eine CPU-Profildatei als auch eine Speicherprofildatei für den Test im aktuellen Verzeichnis: go test -cpuprofile cpu.prof -memprofile mem.prof -bench. 

Laden Sie Live-Profildaten von einem langjährigen Dienst herunter

Wenn Sie einen lang laufenden Webdienst profilieren möchten, können Sie die integrierte HTTP-Schnittstelle zum Bereitstellen von Profildaten verwenden. Fügen Sie irgendwo die folgende Importanweisung hinzu:

import _ "net/http/pprof"

Jetzt können Sie Live-Profildaten von der URL /debug /pprof/ herunterladen. Weitere Informationen finden Sie in der Dokumentation zum Paket net/http/pprof.

Profilerstellung im Code

Sie können Ihrem Code auch eine direkte Profilerstellung hinzufügen, um die vollständige Kontrolle zu erhalten. Zuerst müssen Sie runtime/pprof importieren. Die CPU-Profilerstellung wird durch zwei Aufrufe gesteuert:

  • pprof.StartCPUProfile()
  • pprof.StopCPUProfile()

Die Speicherprofilerstellung erfolgt durch Aufrufen von runtime.GC() gefolgt von pprof.WriteHeapProfile().

Alle Profilierungsfunktionen akzeptieren ein Dateihandle, für dessen ordnungsgemäßes Öffnen und Schließen Sie verantwortlich sind.

Das Beispielprogramm

Um den Profiler in Aktion zu sehen, verwende ich ein Programm, das das Problem 8 von Project Euler löst. Das Problem ist: Suchen Sie bei einer 1.000-stelligen Nummer die 13 benachbarten Ziffern in dieser Nummer, die das größte Produkt haben.

Hier ist eine triviale Lösung, die alle Sequenzen mit 13 Ziffern durchläuft und für jede solche Sequenz alle 13 Ziffern multipliziert und das Ergebnis zurückgibt. Das größte Ergebnis wird gespeichert und schließlich zurückgegeben:

Später, nach der Profilerstellung, werden wir einige Möglichkeiten sehen, um die Leistung mit einer anderen Lösung zu verbessern.

CPU-Profilerstellung

Lassen Sie uns die CPU unseres Programms profilieren. Ich werde die Go-Test-Methode mit diesem Test verwenden:

Beachten Sie, dass ich den Test 100.000 Mal ausführe, da der Go-Profiler ein Stichprobenprofiler ist, bei dem der Code tatsächlich eine erhebliche Zeit (mehrere Millisekunden kumuliert) für jede Codezeile benötigt. Hier ist der Befehl zum Vorbereiten des Profils:

Es dauerte etwas mehr als 13 Sekunden (für 100.000 Iterationen). Um das Profil anzuzeigen, rufen Sie jetzt mit dem Tool pprof go die interaktive Eingabeaufforderung auf. Es gibt viele Befehle und Optionen. Der grundlegendste Befehl ist topN; Mit der Option -cum werden die Top-N-Funktionen angezeigt, deren Ausführung die kumulativste Zeit in Anspruch genommen hat (eine Funktion, deren Ausführung nur sehr wenig Zeit in Anspruch nimmt, die jedoch häufig aufgerufen wird, kann sich oben befinden). Damit beginne ich normalerweise.

Lassen Sie uns die Ausgabe verstehen. Jede Zeile repräsentiert eine Funktion. Ich habe den Pfad zu jeder Funktion aus Platzgründen entfernt, aber er wird in der realen Ausgabe als letzte Spalte angezeigt.

Flat bedeutet die Zeit (oder den Prozentsatz), die innerhalb der Funktion verbracht wird, und Cum steht für kumulativ - die Zeit, die innerhalb der Funktion und aller von ihr aufgerufenen Funktionen verbracht wird. In diesem Fall, testing.tRunner ruft tatsächlich TestFindLargestProduct() auf, das FindLargestProduct() aufruft. Da dort jedoch praktisch keine Zeit verbracht wird, zählt der Stichprobenprofiler seine Flat-Zeit als 0.

Speicherprofilerstellung

Die Speicherprofilerstellung ist ähnlich, außer dass Sie ein Speicherprofil erstellen:

Sie können Ihre Speichernutzung mit demselben Tool analysieren.

Verwenden von pprof zur Optimierung der Geschwindigkeit Ihres Programms

Mal sehen, was wir tun können, um das Problem schneller zu lösen. Wenn wir uns das Profil ansehen, sehen wir, dass calcProduct() 8,17% der flachen Laufzeit beansprucht, während makeSlice(), das von calcProduct() aufgerufen wird, 72% benötigt (kumulativ, weil es andere Funktionen aufruft). Dies gibt einen ziemlich guten Hinweis darauf, was wir optimieren müssen. Was macht der Code? Für jede Folge von 13 benachbarten Zahlen wird ein Slice zugewiesen:

Das sind fast 1.000 Mal pro Lauf und wir laufen 100.000 Mal. Speicherzuordnungen sind langsam. In diesem Fall muss nicht jedes Mal ein neues Slice zugewiesen werden. Tatsächlich ist es überhaupt nicht erforderlich, ein Slice zuzuweisen. Wir können einfach das Eingabearray scannen.

Der folgende Codeausschnitt zeigt, wie das laufende Produkt berechnet wird, indem einfach durch die erste Ziffer der vorherigen Sequenz dividiert und Multiplikation mit cur digit.

Hier ist eine kurze Liste einiger algorithmischer Optimierungen:

  • Berechnung eines laufenden Produkts. Angenommen, wir berechnen das Produkt bei Index N... N+13 und nennen es P (N). Jetzt müssen wir das Produkt bei Index N+1..N+13 berechnen. P (N+1) ist gleich P(N), außer dass die erste Zahl am Index N weg ist und wir die neue Zahl am Index N+14T berücksichtigen müssen. Dies kann erreicht werden, indem das vorherige Produkt durch seine erste Zahl dividiert und mit der neuen Zahl multipliziert wird.
  • Keine Folge von 13 Zahlen mit 0 berechnen (das Produkt ist immer Null).
  • Vermeiden Sie Division oder Multiplikation mit 1.

Das komplette Programm finden Sie hier. Es gibt eine heikle Logik, um die Nullen zu umgehen, aber ansonsten ist es ziemlich einfach. Die Hauptsache ist, dass wir zu Beginn nur ein Array mit 1000 Bytes zuweisen und es per Zeiger (also keine Kopie) an die Funktion findLargestProductInSeries() mit einem Indexbereich übergeben.

Der Test ist der gleiche. Mal sehen, wie wir es mit dem Profil gemacht haben:

Auf Anhieb können wir sehen, dass die Laufzeit von mehr als 13 Sekunden auf weniger als eine Sekunde gesunken ist. Das ist sehr gut. Zeit, hineinzuschauen. Verwenden wir nur top10, das nach flacher Zeit sortiert ist.

Das ist toll. Fast die gesamte Laufzeit wird in unserem Code verbracht. Überhaupt keine Speicherzuordnungen. Mit dem Befehl list können wir tiefer eintauchen und die Anweisungsebene betrachten:

Das ist ziemlich erstaunlich. Sie erhalten eine Aussage für Aussage Timing aller wichtigen Punkte. Beachten Sie, dass der Aufruf in Zeile 73 der funktion f() tatsächlich ein Aufruf von findLargestProductInSeries() ist, den ich aus Platzgründen im Profil umbenannt habe. Dieser Anruf dauert 20 ms. Vielleicht können wir durch Einbetten des Funktionscodes den Funktionsaufruf (einschließlich Zuweisen des Stapels und Kopieren von Argumenten) speichern und diese 20 ms speichern. Es kann andere sinnvolle Optimierungen geben, die diese Ansicht genau bestimmen kann.

Visualisierung

Das Betrachten dieser Textprofile kann für große Programme schwierig sein. Go bietet Ihnen viele Visualisierungsoptionen. Sie müssen Graphviz für den nächsten Abschnitt installieren.

Das pprof-Tool kann Ausgaben in vielen Formaten generieren. Eine der einfachsten Möglichkeiten (SVG-Ausgabe) besteht darin, an der interaktiven Eingabeaufforderung von pprof einfach 'web' einzugeben. Ihr Browser zeigt dann eine schöne Grafik mit dem pink markierten Hot Path an.

Visualization

Flammengraphen

Die eingebauten Grafiken sind nett und hilfreich, aber bei großen Programmen kann es schwierig sein, selbst diese Grafiken zu erkunden. Eines der beliebtesten Tools zur Visualisierung der Leistungsergebnisse ist das Flammendiagramm. Das pprof-Tool unterstützt es noch nicht sofort, aber Sie können bereits mit Ubers go-torch-Tool mit Flammengraphen spielen. Es wird derzeit daran gearbeitet, pprof um eine integrierte Unterstützung für Flammengraphen zu erweitern.

Schlussfolgerung

Go ist eine Systemprogrammiersprache, mit der verteilte Hochleistungssysteme und Datenspeicher erstellt werden. Go bietet eine hervorragende Unterstützung, die die Profilerstellung Ihrer Programme, die Analyse ihrer Leistung und die Visualisierung der Ergebnisse immer besser macht.

Das Go-Team und die Community legen großen Wert darauf, die Leistung zu verbessern. Den vollständigen Quellcode mit drei verschiedenen Algorithmen finden Sie auf GitHub.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.