Profiling
CPU Profiling
CPUプロファイリングのための主なアプローチは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_SNAPSHOT を 1 に設定すると、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.@profile — Macro@profile@profile <expression> は、定期的にバックトレースを取得しながらあなたの式を実行します。これらはバックトレースの内部バッファに追加されます。
Profileのメソッドはエクスポートされておらず、例えばProfile.print()のように呼び出す必要があります。
Profile.clear — Functionclear()内部バッファから既存のバックトレースをクリアします。
Profile.print — Functionprint([io::IO = stdout,] [data::Vector = fetch()], [lidict::Union{LineInfoDict, LineInfoFlatDict} = getdict(data)]; kwargs...)io にプロファイリング結果を出力します(デフォルトは stdout)。data ベクターを提供しない場合、蓄積されたバックトレースの内部バッファが使用されます。
キーワード引数は任意の組み合わせが可能です:
format– バックトレースがツリー構造を示すインデント付き(デフォルト、:tree)で印刷されるか、インデントなし(:flat)で印刷されるかを決定します。C–trueの場合、C および Fortran コードからのバックトレースが表示されます(通常は除外されます)。combine–trueの場合(デフォルト)、同じコード行に対応する命令ポインタがマージされます。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}}– レポートに含めるスナップショットを取得するタスクを指定します。これは、サンプルが収集されるタスクを制御するものではありません。
print([io::IO = stdout,] data::Vector, lidict::LineInfoDict; kwargs...)プロファイリング結果を io に出力します。このバリアントは、以前の retrieve への呼び出しによってエクスポートされた結果を調べるために使用されます。バックトレースのベクター data と行情報の辞書 lidict を提供してください。
有効なキーワード引数の説明については、Profile.print([io], data) を参照してください。
Profile.init — Functioninit(; n::Integer, delay::Real)delay(秒単位で測定)と、スレッドごとに保存される可能性のある命令ポインタの数 n を設定します。各命令ポインタは単一のコード行に対応し、バックトレースは一般的に長い命令ポインタのリストで構成されます。バックトレースごとに6つの命令ポインタがメタデータと2つのNULL終了マーカーを保存するために使用されることに注意してください。現在の設定は、引数なしでこの関数を呼び出すことで取得でき、各設定はキーワードを使用するか、または (n, delay) の順序で独立して設定できます。
Profile.fetch — Functionfetch(;include_meta = true) -> dataプロファイルバックトレースのバッファのコピーを返します。data内の値は、JITコンパイルで使用される正確なメモリアドレスに依存するため、現在のセッションのこのマシンでのみ意味があります。この関数は主に内部使用のためのものであり、ほとんどのユーザーにはretrieveの方が良い選択かもしれません。デフォルトでは、threadidやtaskidなどのメタデータが含まれています。メタデータを削除するには、include_metaをfalseに設定してください。
Profile.retrieve — Functionretrieve(; kwargs...) -> data, lidict"エクスポート"プロファイリング結果をポータブル形式で出力し、すべてのバックトレースのセット(data)と、data内の(セッション固有の)命令ポインタをファイル名、関数名、および行番号を格納するLineInfo値にマッピングする辞書を返します。この関数を使用すると、将来の分析のためにプロファイリング結果を保存できます。
Profile.callers — Functioncallers(funcname, [data, lidict], [filename=<filename>], [linerange=<start:stop>]) -> Vector{Tuple{count, lineinfo}}以前のプロファイリング実行に基づいて、特定の関数を呼び出したのは誰かを特定します。ファイル名(およびオプションで関数が定義されている行番号の範囲)を指定することで、オーバーロードされたメソッドを区別できます。返される値は、呼び出しの回数と呼び出し元に関する行情報を含むベクターです。オプションで、retrieveから取得したバックトレースdataを指定できます。そうでない場合は、現在の内部プロファイルバッファが使用されます。
Profile.clear_malloc_data — Functionclear_malloc_data()--track-allocationオプションを使用してjuliaを実行しているときに、保存されたメモリ割り当てデータをクリアします。テストしたいコマンドを実行して(JITコンパイルを強制するために)、次にclear_malloc_dataを呼び出します。その後、再度コマンドを実行し、Juliaを終了し、結果の*.memファイルを調べます。
Profile.get_peek_duration — Functionget_peek_duration()SIGINFOまたはSIGUSR1を介してトリガーされるプロファイル「peek」の秒単位の持続時間を取得します。
Profile.set_peek_duration — Functionset_peek_duration(t::Float64)プラットフォームに応じて、SIGINFOまたはSIGUSR1を介してトリガーされるプロファイル「peek」の秒数の期間を設定します。
Memory profiling
Profile.Allocs.@profile — MacroProfile.Allocs.@profile [sample_rate=0.1] exprexprの実行中に発生するプロファイルアロケーションを記録し、結果と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では、すべてのケースで型をキャプチャできませんでした。古いバージョンのJuliaでProfile.Allocs.UnknownTypeの型のアロケーションが見られた場合、それはプロファイラがどの型のオブジェクトがアロケートされたのかを知らないことを意味します。これは主に、コンパイラによって生成されたコードからのアロケーションが行われた場合に発生しました。詳細についてはissue #43688を参照してください。
Julia 1.11以降、すべてのアロケーションには報告される型があるはずです。
Profile.Allocs のメソッドはエクスポートされておらず、例えば Profile.Allocs.fetch() のように呼び出す必要があります。
Profile.Allocs.clear — FunctionProfile.Allocs.clear()以前にプロファイルされたすべての割り当て情報をメモリからクリアします。
Profile.Allocs.print — FunctionProfile.Allocs.print([io::IO = stdout,] [data::AllocResults = fetch()]; kwargs...)io にプロファイリング結果を出力します(デフォルトは stdout)。data ベクターを指定しない場合、蓄積されたバックトレースの内部バッファが使用されます。
有効なキーワード引数の説明については Profile.print を参照してください。
Profile.Allocs.fetch — FunctionProfile.Allocs.fetch()記録されたアロケーションを取得し、それらを分析可能なJuliaオブジェクトにデコードします。
Profile.Allocs.start — FunctionProfile.Allocs.start(sample_rate::Real)指定されたサンプルレートでアロケーションの記録を開始します。サンプルレートが1.0の場合はすべてを記録し、0.0の場合は何も記録しません。
Profile.Allocs.stop — FunctionProfile.Allocs.stop()アロケーションの記録を停止します。
Heap Snapshots
Profile.take_heap_snapshot — FunctionProfile.take_heap_snapshot(filepath::String, all_one::Bool=false, streaming=false)
Profile.take_heap_snapshot(all_one::Bool=false; dir::String, streaming=false)ヒープのスナップショットを、Chrome Devtools Heap Snapshot ビューアーが期待する JSON 形式でファイル(デフォルトでは現在のディレクトリに $pid_$timestamp.heapsnapshot という名前で、現在のディレクトリが書き込み不可の場合は tempdir に)に書き込みます。指定された場合は dir に、または指定されたフルファイルパス、または IO ストリームに書き込むこともできます。
all_one が true の場合、すべてのオブジェクトのサイズを 1 として報告し、簡単にカウントできるようにします。そうでない場合は、実際のサイズを報告します。
streaming が true の場合、スナップショットデータを 4 つのファイルにストリーミングし、ファイルパスをプレフィックスとして使用します。これにより、全体のスナップショットをメモリに保持する必要がなくなります。このオプションは、メモリが制約されている設定で使用する必要があります。これらのファイルは、Profile.HeapSnapshot.assemble_snapshot() を呼び出すことで再構成できます。これはオフラインで行うことができます。
注意: パフォーマンスの理由から、streaming=true を設定することを強くお勧めします。部分からスナップショットを再構成するには、全体のスナップショットをメモリに保持する必要があるため、スナップショットが大きい場合、処理中にメモリが不足する可能性があります。ストリーミングを使用すると、ワークロードの実行が完了した後にオフラインでスナップショットを再構成できます。streaming=false(デフォルト、後方互換性のため)でスナップショットを収集しようとし、プロセスが終了した場合、提供されたファイルパスと同じディレクトリに常に部分が保存されるため、後で assemble_snapshot() を介してスナップショットを再構成できます。
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によって生成されたヒープスナップショットの表示には使用できない可能性があります。