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.@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...)

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

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

  • 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, 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() を介して後でスナップショットを再構成することができます。

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によって生成されたヒープスナップショットの表示には使用できない可能性があります。