Profiling

CPU Profiling

Es gibt zwei Hauptansätze zur CPU-Profilierung von Julia-Code:

Via @profile

Wo das Profiling für einen bestimmten Aufruf über das @profile-Makro aktiviert ist.

julia> using Profile

julia> @profile foo()

julia> Profile.print()
Overhead ╎ [+additional indent] Count File:Line; Function
=========================================================
    ╎147  @Base/client.jl:506; _start()
        ╎ 147  @Base/client.jl:318; exec_options(opts::Base.JLOptions)
...

Triggered During Execution

Aufgaben, die bereits ausgeführt werden, können auch für einen festen Zeitraum zu einem beliebigen benutzergesteuerten Zeitpunkt profiliert werden.

Um das Profiling auszulösen:

  • MacOS & FreeBSD (BSD-basierte Plattformen): Verwenden Sie ctrl-t oder senden Sie ein SIGINFO-Signal an den Julia-Prozess, d.h. % kill -INFO $julia_pid
  • Linux: Senden Sie ein SIGUSR1-Signal an den Julia-Prozess, d.h. % kill -USR1 $julia_pid
  • Windows: Derzeit nicht unterstützt.

Zuerst wird ein einzelner Stack-Trace zum Zeitpunkt des Signals angezeigt, dann wird ein 1-Sekunden-Profil gesammelt, gefolgt von dem Profilbericht am nächsten Ertragspunkt, der möglicherweise am Ende der Aufgabe für Code ohne Ertragspunkte, z.B. enge Schleifen, liegt.

Optional setzen Sie die Umgebungsvariable JULIA_PROFILE_PEEK_HEAP_SNAPSHOT auf 1, um auch automatisch einen heap snapshot zu sammeln.

julia> foo()
##== the user sends a trigger while foo is running ==##
load: 2.53  cmd: julia 88903 running 6.16u 0.97s

======================================================================================
Information request received. A stacktrace will print followed by a 1.0 second profile
======================================================================================

signal (29): Information request: 29
__psynch_cvwait at /usr/lib/system/libsystem_kernel.dylib (unknown line)
_pthread_cond_wait at /usr/lib/system/libsystem_pthread.dylib (unknown line)
...

======================================================================
Profile collected. A report will print if the Profile module is loaded
======================================================================

Overhead ╎ [+additional indent] Count File:Line; Function
=========================================================
Thread 1 Task 0x000000011687c010 Total snapshots: 572. Utilization: 100%
   ╎147 @Base/client.jl:506; _start()
       ╎ 147 @Base/client.jl:318; exec_options(opts::Base.JLOptions)
...

Thread 2 Task 0x0000000116960010 Total snapshots: 572. Utilization: 0%
   ╎572 @Base/task.jl:587; task_done_hook(t::Task)
      ╎ 572 @Base/task.jl:879; wait()
...

Customization

Die Dauer des Profilings kann über Profile.set_peek_duration angepasst werden.

Der Profilbericht ist nach Thread und Aufgabe unterteilt. Übergeben Sie eine Funktion ohne Argumente an Profile.peek_report[], um dies zu überschreiben. z.B. Profile.peek_report[] = () -> Profile.print(), um jegliche Gruppierung zu entfernen. Dies könnte auch von einem externen Profil-Datenverbraucher überschrieben werden.

Reference

Profile.@profileMacro
@profile

@profile <Ausdruck> führt Ihren Ausdruck aus und nimmt dabei periodisch Rückverfolgungen vor. Diese werden einem internen Puffer von Rückverfolgungen hinzugefügt.

source

Die Methoden in Profile sind nicht exportiert und müssen z.B. als Profile.print() aufgerufen werden.

Profile.clearFunction
clear()

Löscht alle vorhandenen Rückverfolgungen aus dem internen Puffer.

source
Profile.printFunction
print([io::IO = stdout,] [data::Vector = fetch()], [lidict::Union{LineInfoDict, LineInfoFlatDict} = getdict(data)]; kwargs...)

Gibt Profiling-Ergebnisse an io aus (standardmäßig stdout). Wenn Sie keinen data-Vektor angeben, wird der interne Puffer von angesammelten Backtraces verwendet.

Die Schlüsselwortargumente können eine beliebige Kombination aus folgenden sein:

  • format – Bestimmt, ob Backtraces mit (Standard, :tree) oder ohne (:flat) Einrückung, die die Baumstruktur anzeigt, ausgegeben werden.
  • C – Wenn true, werden Backtraces aus C- und Fortran-Code angezeigt (normalerweise werden sie ausgeschlossen).
  • combine – Wenn true (Standard), werden die Befehlszeiger zusammengeführt, die der gleichen Codezeile entsprechen.
  • maxdepth – Begrenzung der Tiefe über maxdepth im :tree-Format.
  • sortedby – Steuert die Reihenfolge im :flat-Format. :filefuncline (Standard) sortiert nach der Quellzeile, :count sortiert in der Reihenfolge der Anzahl gesammelter Proben, und :overhead sortiert nach der Anzahl der Proben, die jede Funktion für sich selbst verursacht hat.
  • groupby – Steuert die Gruppierung über Aufgaben und Threads oder keine Gruppierung. Optionen sind :none (Standard), :thread, :task, [:thread, :task] oder [:task, :thread], wobei die letzten beiden eine geschachtelte Gruppierung bieten.
  • noisefloor – Begrenzung von Frames, die den heuristischen Geräuschpegel der Probe überschreiten (gilt nur für das Format :tree). Ein empfohlener Wert, den Sie ausprobieren können, ist 2.0 (der Standardwert ist 0). Dieses Parameter verbirgt Proben, für die n <= noisefloor * √N, wobei n die Anzahl der Proben in dieser Zeile und N die Anzahl der Proben für den Aufrufer ist.
  • mincount – Begrenzung der Ausgabe auf nur die Zeilen mit mindestens mincount Vorkommen.
  • recur – Steuert die Handhabung der Rekursion im :tree-Format. :off (Standard) gibt den Baum normal aus. :flat hingegen komprimiert jede Rekursion (nach ip) und zeigt die ungefähren Auswirkungen der Umwandlung jeder Selbstrekursion in einen Iterator. :flatc macht dasselbe, schließt jedoch auch das Zusammenfassen von C-Frames ein (kann seltsame Dinge um jl_apply herum tun).
  • threads::Union{Int,AbstractVector{Int}} – Geben Sie an, von welchen Threads Schnappschüsse im Bericht enthalten sein sollen. Beachten Sie, dass dies nicht steuert, von welchen Threads Proben gesammelt werden (die möglicherweise auch auf einem anderen Computer gesammelt wurden).
  • tasks::Union{Int,AbstractVector{Int}} – Geben Sie an, von welchen Aufgaben Schnappschüsse im Bericht enthalten sein sollen. Beachten Sie, dass dies nicht steuert, innerhalb welcher Aufgaben Proben gesammelt werden.
Julia 1.8

Die Schlüsselwortargumente groupby, threads und tasks wurden in Julia 1.8 eingeführt.

Note

Profiling unter Windows ist auf den Hauptthread beschränkt. Andere Threads wurden nicht beprobt und werden im Bericht nicht angezeigt.

source
print([io::IO = stdout,] data::Vector, lidict::LineInfoDict; kwargs...)

Gibt die Profilierungsergebnisse an io aus. Diese Variante wird verwendet, um Ergebnisse zu untersuchen, die von einem vorherigen Aufruf von retrieve exportiert wurden. Übergeben Sie den Vektor data von Backtraces und ein Wörterbuch lidict mit Zeileninformationen.

Siehe Profile.print([io], data) für eine Erklärung der gültigen Schlüsselwortargumente.

source
Profile.initFunction
init(; n::Integer, delay::Real)

Konfigurieren Sie die delay zwischen Rückverfolgungen (gemessen in Sekunden) und die Anzahl n der Befehlszeiger, die pro Thread gespeichert werden können. Jeder Befehlszeiger entspricht einer einzelnen Codezeile; Rückverfolgungen bestehen im Allgemeinen aus einer langen Liste von Befehlszeigern. Beachten Sie, dass 6 Plätze für Befehlszeiger pro Rückverfolgung verwendet werden, um Metadaten und zwei NULL-Endmarkierungen zu speichern. Die aktuellen Einstellungen können abgerufen werden, indem Sie diese Funktion ohne Argumente aufrufen, und jede kann unabhängig mit Schlüsselwörtern oder in der Reihenfolge (n, delay) festgelegt werden.

source
Profile.fetchFunction
fetch(;include_meta = true) -> data

Gibt eine Kopie des Puffers von Profil-Backtraces zurück. Beachten Sie, dass die Werte in data nur auf dieser Maschine in der aktuellen Sitzung Bedeutung haben, da sie von den genauen Speicheradressen abhängen, die beim JIT-Kompilieren verwendet werden. Diese Funktion ist hauptsächlich für den internen Gebrauch; retrieve könnte für die meisten Benutzer eine bessere Wahl sein. Standardmäßig sind Metadaten wie threadid und taskid enthalten. Setzen Sie include_meta auf false, um Metadaten zu entfernen.

source
Profile.retrieveFunction
retrieve(; kwargs...) -> data, lidict

"Exports" Profiling-Ergebnisse in einem tragbaren Format, das die Menge aller Backtraces (data) und ein Wörterbuch zurückgibt, das die (sessionspezifischen) Instruktionszeiger in data den LineInfo-Werten zuordnet, die den Dateinamen, den Funktionsnamen und die Zeilennummer speichern. Diese Funktion ermöglicht es Ihnen, Profiling-Ergebnisse für zukünftige Analysen zu speichern.

source
Profile.callersFunction
callers(funcname, [data, lidict], [filename=<filename>], [linerange=<start:stop>]) -> Vector{Tuple{count, lineinfo}}

Geben Sie einen vorherigen Profiling-Lauf an, um zu bestimmen, wer eine bestimmte Funktion aufgerufen hat. Das Bereitstellen des Dateinamens (und optional eines Bereichs von Zeilennummern, über die die Funktion definiert ist) ermöglicht es Ihnen, eine überladene Methode zu unterscheiden. Der zurückgegebene Wert ist ein Vektor, der eine Anzahl der Aufrufe und Informationen über die Zeile des Aufrufers enthält. Man kann optional den Backtrace data bereitstellen, der von retrieve erhalten wurde; andernfalls wird der aktuelle interne Profilpuffer verwendet.

source
Profile.clear_malloc_dataFunction
clear_malloc_data()

Löscht alle gespeicherten Speicherzuweisungsdaten, wenn Julia mit --track-allocation ausgeführt wird. Führen Sie die Befehle aus, die Sie testen möchten (um die JIT-Kompilierung zu erzwingen), und rufen Sie dann clear_malloc_data auf. Führen Sie dann Ihre Befehle erneut aus, beenden Sie Julia und überprüfen Sie die resultierenden *.mem-Dateien.

source
Profile.get_peek_durationFunction
get_peek_duration()

Holen Sie die Dauer in Sekunden des Profils "peek" ab, das über SIGINFO oder SIGUSR1 ausgelöst wird, je nach Plattform.

source
Profile.set_peek_durationFunction
set_peek_duration(t::Float64)

Setzen Sie die Dauer in Sekunden des Profils "peek", das über SIGINFO oder SIGUSR1 ausgelöst wird, je nach Plattform.

source

Memory profiling

Profile.Allocs.@profileMacro
Profile.Allocs.@profile [sample_rate=0.1] expr

Profilieren Sie die Allokationen, die während expr stattfinden, und geben Sie sowohl das Ergebnis als auch eine AllocResults-Struktur zurück.

Eine Abtastrate von 1.0 erfasst alles; 0.0 erfasst nichts.

julia> Profile.Allocs.@profile sample_rate=0.01 peakflops()
1.03733270279065e11

julia> results = Profile.Allocs.fetch()

julia> last(sort(results.allocs, by=x->x.size))
Profile.Allocs.Alloc(Vector{Any}, Base.StackTraces.StackFrame[_new_array_ at array.c:127, ...], 5576)

Siehe das Profiling-Tutorial in der Julia-Dokumentation für weitere Informationen.

Julia 1.11

Ältere Versionen von Julia konnten in allen Fällen keine Typen erfassen. In älteren Versionen von Julia, wenn Sie eine Allokation des Typs Profile.Allocs.UnknownType sehen, bedeutet dies, dass der Profiler nicht weiß, welchen Typ von Objekt erfasst wurde. Dies geschah hauptsächlich, wenn die Allokation aus generiertem Code stammte, der vom Compiler erzeugt wurde. Siehe issue #43688 für weitere Informationen.

Seit Julia 1.11 sollten alle Allokationen einen gemeldeten Typ haben.

Julia 1.8

Der Allokationsprofiler wurde in Julia 1.8 hinzugefügt.

source

Die Methoden in Profile.Allocs sind nicht exportiert und müssen z.B. als Profile.Allocs.fetch() aufgerufen werden.

Profile.Allocs.clearFunction
Profile.Allocs.clear()

Löschen Sie alle zuvor profilierten Zuweisungsinformationen aus dem Speicher.

source
Profile.Allocs.printFunction
Profile.Allocs.print([io::IO = stdout,] [data::AllocResults = fetch()]; kwargs...)

Gibt die Profilierungsergebnisse an io aus (standardmäßig stdout). Wenn Sie keinen data-Vektor angeben, wird der interne Puffer der angesammelten Backtraces verwendet.

Siehe Profile.print für eine Erklärung der gültigen Schlüsselwortargumente.

source
Profile.Allocs.fetchFunction
Profile.Allocs.fetch()

Rufen Sie die aufgezeichneten Zuweisungen ab und decodieren Sie sie in Julia-Objekte, die analysiert werden können.

source
Profile.Allocs.startFunction
Profile.Allocs.start(sample_rate::Real)

Beginne mit der Aufzeichnung von Zuweisungen mit der angegebenen Abtastrate. Eine Abtastrate von 1.0 zeichnet alles auf; 0.0 zeichnet nichts auf.

source

Heap Snapshots

Profile.take_heap_snapshotFunction
Profile.take_heap_snapshot(filepath::String, all_one::Bool=false, streaming=false)
Profile.take_heap_snapshot(all_one::Bool=false; dir::String, streaming=false)

Schreiben Sie einen Snapshot des Heaps im JSON-Format, das vom Chrome Devtools Heap Snapshot Viewer (.heapsnapshot-Erweiterung) erwartet wird, in eine Datei ($pid_$timestamp.heapsnapshot) im aktuellen Verzeichnis standardmäßig (oder tempdir, wenn das aktuelle Verzeichnis nicht beschreibbar ist), oder in dir, wenn angegeben, oder den angegebenen vollständigen Dateipfad oder IO-Stream.

Wenn all_one wahr ist, berichten Sie die Größe jedes Objekts als eins, damit sie leicht gezählt werden können. Andernfalls berichten Sie die tatsächliche Größe.

Wenn streaming wahr ist, streamen wir die Snapshot-Daten in vier Dateien, wobei filepath als Präfix verwendet wird, um zu vermeiden, dass der gesamte Snapshot im Speicher gehalten werden muss. Diese Option sollte für jede Einstellung verwendet werden, bei der Ihr Speicher begrenzt ist. Diese Dateien können dann durch Aufrufen von Profile.HeapSnapshot.assemble_snapshot() wieder zusammengesetzt werden, was offline erfolgen kann.

HINWEIS: Wir empfehlen dringend, streaming=true aus Leistungsgründen einzustellen. Das Rekonstruieren des Snapshots aus den Teilen erfordert, dass der gesamte Snapshot im Speicher gehalten wird. Wenn der Snapshot also groß ist, können Sie beim Verarbeiten des Snapshots ohne Speicherplatz dastehen. Streaming ermöglicht es Ihnen, den Snapshot offline zu rekonstruieren, nachdem Ihre Arbeitslast abgeschlossen ist. Wenn Sie versuchen, einen Snapshot mit streaming=false (dem Standardwert, um die Abwärtskompatibilität zu gewährleisten) zu sammeln und Ihr Prozess beendet wird, beachten Sie, dass dies immer die Teile im selben Verzeichnis wie Ihren angegebenen Dateipfad speichert, sodass Sie den Snapshot nachträglich über assemble_snapshot() rekonstruieren können. ```

source

Die Methoden in Profile sind nicht exportiert und müssen z.B. als Profile.take_heap_snapshot() aufgerufen werden.

julia> using Profile

julia> Profile.take_heap_snapshot("snapshot.heapsnapshot")

Verfolgt und zeichnet Julia-Objekte im Heap auf. Dies erfasst nur Objekte, die dem Julia-Garbage-Collector bekannt sind. Speicher, der von externen Bibliotheken zugewiesen wird, die nicht vom Garbage-Collector verwaltet werden, wird im Snapshot nicht angezeigt.

Um OOM zu vermeiden, während der Snapshot aufgenommen wird, haben wir eine Streaming-Option hinzugefügt, um den Heap-Snapshot in vier Dateien zu streamen,

julia> using Profile

julia> Profile.take_heap_snapshot("snapshot"; streaming=true)

wo "snapshot" der Dateipfad als Präfix für die generierten Dateien ist.

Sobald die Snapshot-Dateien erstellt sind, können sie offline mit dem folgenden Befehl zusammengefügt werden:

julia> using Profile

julia> Profile.HeapSnapshot.assemble_snapshot("snapshot", "snapshot.heapsnapshot")

Die resultierende Heap-Snapshot-Datei kann in Chrome DevTools hochgeladen werden, um sie anzuzeigen. Weitere Informationen finden Sie unter chrome devtools docs. Eine Alternative zur Analyse von Chromium-Heap-Snapshots ist die VS Code-Erweiterung ms-vscode.vscode-js-profile-flame.

Die Firefox-Heap-Snapshots haben ein anderes Format, und Firefox kann derzeit nicht zum Anzeigen der von Julia generierten Heap-Snapshots verwendet werden.