Profiling

CPU Profiling

CPUプロファイリングのJuliaコードには主に2つのアプローチがあります:

Via @profile

@profile マクロを介して特定の呼び出しに対してプロファイリングが有効になっている場所。

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

実行中のタスクは、ユーザーがトリガーした任意の時点で、固定の時間期間にわたってプロファイルすることもできます。

プロファイリングをトリガーするには:

  • MacOS & FreeBSD (BSDベースのプラットフォーム): ctrl-tを使用するか、SIGINFO信号をjuliaプロセスに送信します。つまり、% kill -INFO $julia_pid
  • Linux: juliaプロセスにSIGUSR1シグナルを送信します。つまり、% kill -USR1 $julia_pidです。
  • Windows: 現在サポートされていません。

最初に、信号が発生した瞬間の単一のスタックトレースが表示され、その後1秒間のプロファイルが収集され、次のイールドポイントでのプロファイルレポートが続きます。これは、イールドポイントのないコード(例えば、タイトなループ)の場合、タスクの完了時に発生する可能性があります。

オプションで環境変数 JULIA_PROFILE_PEEK_HEAP_SNAPSHOT1 に設定すると、heap snapshot も自動的に収集されます。

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

プロファイリングの期間は、Profile.set_peek_durationを介して調整できます。

プロファイルレポートはスレッドとタスクによって分割されています。これを上書きするには、引数なしの関数をProfile.peek_report[]に渡します。つまり、Profile.peek_report[] = () -> Profile.print()のようにして、グループ化を削除します。これは外部のプロファイルデータ消費者によっても上書きされる可能性があります。

Reference

Profile.@profileMacro
@profile

@profile <expression> は、定期的にバックトレースを取得しながらあなたの式を実行します。これらは内部のバックトレースのバッファに追加されます。

source

Profileのメソッドはエクスポートされておらず、例えばProfile.print()のように呼び出す必要があります。

Profile.clearFunction
clear()

内部バッファから既存のバックトレースをクリアします。

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

io にプロファイリング結果を出力します(デフォルトは stdout)。data ベクターを提供しない場合、蓄積されたバックトレースの内部バッファが使用されます。パスはサポートされているターミナルでクリック可能なリンクであり、行番号付きの JULIA_EDITOR に特化されているか、エディタが設定されていない場合はファイルリンクのみです。

キーワード引数は任意の組み合わせが可能です:

  • format – バックトレースがインデント付き(デフォルト、:tree)またはインデントなし(:flat)で表示されるかどうかを決定します。
  • Ctrue の場合、C および Fortran コードからのバックトレースが表示されます(通常は除外されます)。
  • combinetrue(デフォルト)の場合、同じ行のコードに対応する命令ポインタがマージされます。
  • maxdepth:tree 形式で maxdepth より深い深さを制限します。
  • sortedby:flat 形式での順序を制御します。:filefuncline(デフォルト)はソース行でソートし、:count は収集されたサンプルの数の順にソートし、:overhead は各関数によって発生したサンプルの数でソートします。
  • groupby – タスクとスレッドのグループ化を制御します。オプションは :none(デフォルト)、:thread:task[:thread, :task]、または [:task, :thread] で、最後の2つはネストされたグループ化を提供します。
  • noisefloor – サンプルのヒューリスティックノイズフロアを超えるフレームを制限します(:tree 形式にのみ適用されます)。これに対して試すべき推奨値は 2.0 です(デフォルトは 0)。このパラメータは、n <= noisefloor * √N の場合にサンプルを隠します。ここで、n はこの行のサンプル数、N は呼び出し元のサンプル数です。
  • mincount – 出力を mincount 回以上の出現がある行のみに制限します。
  • recur:tree 形式での再帰処理を制御します。:off(デフォルト)はツリーを通常通り表示します。:flat は再帰を圧縮し(ip によって)、自己再帰をイテレータに変換した場合の近似効果を示します。:flatc は同様のことを行いますが、C フレームの圧縮も含まれます(jl_apply の周りで奇妙なことが起こる可能性があります)。
  • threads::Union{Int,AbstractVector{Int}} – レポートにスナップショットを含めるスレッドを指定します。これは、サンプルが収集されるスレッドを制御するものではありません(別のマシンで収集された可能性もあります)。
  • tasks::Union{Int,AbstractVector{Int}} – レポートにスナップショットを含めるタスクを指定します。これは、サンプルが収集されるタスクを制御するものではありません。
Julia 1.8

groupbythreads、および tasks キーワード引数は Julia 1.8 で導入されました。

Note

Windows でのプロファイリングはメインスレッドに制限されています。他のスレッドはサンプリングされておらず、レポートには表示されません。

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

プロファイリング結果を io に出力します。このバリアントは、以前の retrieve の呼び出しによってエクスポートされた結果を調べるために使用されます。バックトレースのベクター data と行情報の辞書 lidict を提供してください。

有効なキーワード引数の説明については Profile.print([io], data) を参照してください。

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

delayをバックトレース間の遅延(秒単位)として設定し、スレッドごとに保存できる命令ポインタの数nを設定します。各命令ポインタは単一のコード行に対応し、バックトレースは一般的に長い命令ポインタのリストで構成されます。バックトレースごとに6つの命令ポインタがメタデータと2つのNULL終了マーカーを保存するために使用されることに注意してください。現在の設定は、引数なしでこの関数を呼び出すことで取得でき、各設定はキーワードを使用するか、または順番に(n, delay)の形式で独立して設定できます。

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

プロファイルバックトレースのバッファのコピーを返します。data内の値は、JITコンパイルで使用される正確なメモリアドレスに依存するため、現在のセッションのこのマシンでのみ意味があります。この関数は主に内部使用のためのものであり、ほとんどのユーザーにはretrieveの方が良い選択かもしれません。デフォルトでは、threadidやtaskidなどのメタデータが含まれています。メタデータを削除するには、include_metafalseに設定してください。

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

"Exports" プロファイリング結果をポータブル形式で出力し、すべてのバックトレースのセット (data) と、data 内の(セッション固有の)命令ポインタをファイル名、関数名、行番号を格納する LineInfo 値にマッピングする辞書を返します。この関数を使用すると、将来の分析のためにプロファイリング結果を保存できます。

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

以前のプロファイリング実行に基づいて、特定の関数を呼び出したのは誰かを特定します。ファイル名(およびオプションで関数が定義されている行番号の範囲)を指定することで、オーバーロードされたメソッドを区別できます。返される値は、呼び出しの回数と呼び出し元に関する行情報を含むベクターです。オプションで、retrieveから取得したバックトレースdataを提供できます。そうでない場合は、現在の内部プロファイルバッファが使用されます。

source
Profile.clear_malloc_dataFunction
clear_malloc_data()

--track-allocationオプションを使用してjuliaを実行しているときに、保存されたメモリ割り当てデータをクリアします。テストしたいコマンドを実行して(JITコンパイルを強制するために)、次にclear_malloc_dataを呼び出します。その後、再度コマンドを実行し、Juliaを終了し、結果の*.memファイルを調べます。

source
Profile.get_peek_durationFunction
get_peek_duration()

SIGINFOまたはSIGUSR1を介してトリガーされるプロファイル「peek」の秒単位の期間を取得します。

source
Profile.set_peek_durationFunction
set_peek_duration(t::Float64)

SIGINFOまたはSIGUSR1を介してトリガーされるプロファイル「peek」の秒単位の持続時間を設定します。プラットフォームに応じて異なります。

source

Memory profiling

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

exprの実行中に発生するプロファイルアロケーションを記録し、結果とAllocResults構造体の両方を返します。

サンプルレートが1.0の場合はすべてを記録し、0.0の場合は何も記録しません。

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)

詳細については、Juliaのドキュメントにあるプロファイリングチュートリアルを参照してください。

Julia 1.11

古いバージョンのJuliaでは、すべてのケースで型をキャプチャできませんでした。古いバージョンのJuliaでProfile.Allocs.UnknownTypeの型のアロケーションが見られた場合、それはプロファイラがどの型のオブジェクトがアロケートされたのかを知らないことを意味します。これは主に、コンパイラによって生成されたコードからのアロケーションが原因で発生しました。詳細についてはissue #43688を参照してください。

Julia 1.11以降、すべてのアロケーションには報告される型があるはずです。

Julia 1.8

アロケーションプロファイラはJulia 1.8で追加されました。

source

Profile.Allocs のメソッドはエクスポートされておらず、例えば Profile.Allocs.fetch() のように呼び出す必要があります。

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

以前にプロファイルされたすべてのアロケーション情報をメモリからクリアします。

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

io にプロファイリング結果を出力します(デフォルトでは stdout)。data ベクターを指定しない場合、蓄積されたバックトレースの内部バッファが使用されます。

有効なキーワード引数の説明については Profile.print を参照してください。

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

記録されたアロケーションを取得し、それらを分析可能なJuliaオブジェクトにデコードします。

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

指定されたサンプルレートでアロケーションの記録を開始します。サンプルレートが1.0の場合はすべてを記録し、0.0の場合は何も記録しません。

source

Heap Snapshots

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

ヒープのスナップショットを、Chrome Devtools Heap Snapshot ビューアーが期待する JSON 形式でファイルに書き込みます(.heapsnapshot 拡張子)。デフォルトでは現在のディレクトリにファイル($pid_$timestamp.heapsnapshot)を書き込みます(または現在のディレクトリが書き込み不可の場合は tempdir に)。また、dir が指定されている場合はそこに、または指定されたフルファイルパス、または IO ストリームに書き込みます。

all_one が true の場合、すべてのオブジェクトのサイズを 1 として報告し、簡単にカウントできるようにします。そうでない場合は、実際のサイズを報告します。

redact_data が true の場合(デフォルト)、オブジェクトの内容は出力されません。

streaming が true の場合、スナップショットデータを 4 つのファイルにストリーミングし、filepath をプレフィックスとして使用します。これにより、全体のスナップショットをメモリに保持する必要がなくなります。このオプションは、メモリが制約されている設定で使用する必要があります。これらのファイルは、Profile.HeapSnapshot.assemble_snapshot() を呼び出すことで再構成できます。これはオフラインで行うことができます。

注意: パフォーマンスの理由から、streaming=true を設定することを強く推奨します。部分からスナップショットを再構成するには、全体のスナップショットをメモリに保持する必要があるため、スナップショットが大きい場合、処理中にメモリが不足する可能性があります。ストリーミングを使用すると、ワークロードの実行が完了した後にオフラインでスナップショットを再構成できます。streaming=false(デフォルト、後方互換性のため)でスナップショットを収集しようとし、プロセスが終了した場合、提供されたファイルパスと同じディレクトリに常に部分が保存されるため、assemble_snapshot() を介して後でスナップショットを再構成できます。

source

Profileのメソッドはエクスポートされておらず、例えばProfile.take_heap_snapshot()のように呼び出す必要があります。

julia> using Profile

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

ヒープ上のJuliaオブジェクトをトレースおよび記録します。これは、Juliaガベージコレクタによって知られているオブジェクトのみを記録します。ガベージコレクタによって管理されていない外部ライブラリによって割り当てられたメモリは、スナップショットには表示されません。

スナップショットを記録中にOOMを回避するために、ヒープスナップショットを4つのファイルにストリーミングするオプションを追加しました。

julia> using Profile

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

「snapshot」は生成されたファイルのプレフィックスとしてのファイルパスです。

スナップショットファイルが生成されると、次のコマンドを使用してオフラインで組み立てることができます:

julia> using Profile

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

結果として得られたヒープスナップショットファイルは、Chrome DevToolsにアップロードして表示できます。詳細については、chrome devtools docsを参照してください。Chromiumヒープスナップショットを分析するための別の方法は、VS Code拡張機能ms-vscode.vscode-js-profile-flameを使用することです。

Firefoxのヒープスナップショットは異なる形式であり、現在FirefoxはJuliaによって生成されたヒープスナップショットの表示には使用できない可能性があります。