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秒間のプロファイルが収集され、次のイールドポイントでのプロファイルレポートが続きます。これは、イールドポイントのないコード(例えば、タイトなループ)の場合、タスクの完了時に行われることがあります。
Optionally set environment variable JULIA_PROFILE_PEEK_HEAP_SNAPSHOT
to 1
to also automatically collect a 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}}
– レポートに含めるスナップショットを取得するタスクを指定します。これは、サンプルが収集されるタスクを制御するものではありません。
groupby
、threads
、およびtasks
キーワード引数はJulia 1.8で導入されました。
Windowsでのプロファイリングはメインスレッドに制限されています。他のスレッドはサンプリングされておらず、レポートには表示されません。
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
"Exports" プロファイリング結果をポータブルな形式で出力し、すべてのバックトレースのセット(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] 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では、すべてのケースで型をキャプチャできませんでした。古いバージョンのJuliaでProfile.Allocs.UnknownType
の型のアロケーションが見られた場合、それはプロファイラがどの型のオブジェクトがアロケートされたのかを知らないことを意味します。これは主に、コンパイラによって生成されたコードからのアロケーションが原因で発生しました。詳細についてはissue #43688を参照してください。
Julia 1.11以降、すべてのアロケーションには報告される型があるはずです。
アロケーションプロファイラはJulia 1.8で追加されました。
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によって生成されたヒープスナップショットの表示には使用できない可能性があります。