性能分析

CPU 性能分析

有两种主要方法可以对 Julia 代码进行 CPU 性能分析

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

在执行期间触发

已经运行的任务也可以在任何用户触发的时刻对固定时间段进行性能分析。

要触发性能分析

  • MacOS 和 FreeBSD(基于 BSD 的平台):使用 ctrl-t 或将 SIGINFO 信号传递给 Julia 进程,例如 % kill -INFO $julia_pid
  • Linux:将 SIGUSR1 信号传递给 Julia 进程,例如 % kill -USR1 $julia_pid
  • Windows:目前不支持。

首先,显示在抛出信号时的单个堆栈跟踪,然后收集 1 秒的性能分析数据,然后在下一个 yield 点显示性能分析报告,对于没有 yield 点的代码(例如紧凑循环),该点可能在任务完成后出现。

可以选择将环境变量 JULIA_PROFILE_PEEK_HEAP_SNAPSHOT 设置为 1 以自动收集 堆快照

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

自定义

可以通过 Profile.set_peek_duration 调整性能分析的持续时间。

性能分析报告按线程和任务细分。将无参数函数传递给 Profile.peek_report[] 以覆盖此设置。例如,Profile.peek_report[] = () -> Profile.print() 用于删除任何分组。这也可以由外部性能分析数据使用者覆盖。

参考

Profile.@profile
@profile

@profile <expression> 在定期获取回溯的同时运行您的表达式。这些回溯将追加到回溯的内部缓冲区。

源代码

Profile 中的方法未导出,需要以 Profile.print() 的方式调用。

Profile.print函数
print([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],其中最后两个提供嵌套分组。

  • 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

groupbythreadstasks 关键字参数在 Julia 1.8 中引入。

注意

Windows 上的性能分析仅限于主线程。其他线程尚未采样,并且不会显示在报告中。

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

将性能分析结果打印到 io。此变体用于检查先前对 retrieve 的调用导出的结果。提供回溯的向量 data 和行信息的字典 lidict

有关有效关键字参数的说明,请参见 Profile.print([io], data)

源代码
Profile.init函数
init(; n::Integer, delay::Real)

配置回溯之间的 delay(以秒为单位)以及每个线程可能存储的指令指针数量 n。每个指令指针对应于代码的一行;回溯通常包含一个很长的指令指针列表。请注意,每个回溯的指令指针使用 6 个空间来存储元数据,以及两个 NULL 结束标记。可以通过不带参数调用此函数来获取当前设置,并且可以使用关键字或按 (n, delay) 的顺序独立设置每个设置。

源代码
Profile.fetch函数
fetch(;include_meta = true) -> data

返回性能分析回溯缓冲区的副本。请注意,data 中的值仅在当前会话的这台机器上才有意义,因为它取决于 JIT 编译中使用的确切内存地址。此函数主要用于内部使用;对于大多数用户来说,retrieve 可能是更好的选择。默认情况下,包括线程 ID 和任务 ID 等元数据。将 include_meta 设置为 false 以去除元数据。

源代码
Profile.retrieve函数
retrieve(; kwargs...) -> data, lidict

以可移植格式“导出”性能分析结果,返回所有回溯的集合(data)以及一个字典,该字典将 data 中的(特定于会话的)指令指针映射到存储文件名、函数名和行号的 LineInfo 值。此函数允许您保存性能分析结果以供将来分析。

源代码
Profile.callers函数
callers(funcname, [data, lidict], [filename=<filename>], [linerange=<start:stop>]) -> Vector{Tuple{count, lineinfo}}

给定之前的性能分析运行,确定谁调用了特定函数。提供文件名(以及可选的函数定义所在的行号范围)可以帮助您区分重载方法。返回值是一个向量,包含调用次数和调用方行信息的计数。可以选择提供从retrieve获取的回溯data;否则,将使用当前的内部性能分析缓冲区。

源代码
Profile.clear_malloc_data函数
clear_malloc_data()

在使用--track-allocation运行 Julia 时,清除任何存储的内存分配数据。执行要测试的命令(以强制 JIT 编译),然后调用clear_malloc_data。然后再次执行您的命令,退出 Julia,并检查生成的*.mem文件。

源代码
Profile.get_peek_duration函数
get_peek_duration()

获取通过SIGINFOSIGUSR1触发的性能分析“窥视”的持续时间(以秒为单位),具体取决于平台。

源代码
Profile.set_peek_duration函数
set_peek_duration(t::Float64)

设置通过SIGINFOSIGUSR1触发的性能分析“窥视”的持续时间(以秒为单位),具体取决于平台。

源代码

内存分析

Profile.Allocs.@profile
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)

目前,可视化这些内容的最佳方法是使用PProf.jl包,通过调用PProf.Allocs.pprof

注意

分配分析器的当前实现并非捕获所有分配的类型。分析器无法捕获类型的分配表示为具有Profile.Allocs.UnknownType类型。

您可以在这里阅读有关缺少类型和改进计划的更多信息:https://github.com/JuliaLang/julia/issues/43688

Julia 1.8

分配分析器在 Julia 1.8 中添加。

源代码

Profile.Allocs中的方法未导出,需要例如以Profile.Allocs.fetch()的方式调用。

Profile.Allocs.start函数
Profile.Allocs.start(sample_rate::Real)

以给定的采样率开始记录分配。采样率为1.0将记录所有内容;0.0将不记录任何内容。

源代码

堆快照

Profile.take_heap_snapshot函数
Profile.take_heap_snapshot(io::IOStream, all_one::Bool=false)
Profile.take_heap_snapshot(filepath::String, all_one::Bool=false)
Profile.take_heap_snapshot(all_one::Bool=false; dir::String)

将堆的快照(采用 Chrome Devtools 堆快照查看器期望的 JSON 格式(.heapsnapshot 扩展名))写入文件(默认情况下为当前目录中的$pid_$timestamp.heapsnapshot,如果当前目录不可写则为tempdir),或者如果给出则写入dir,或者写入给定的完整文件路径,或者写入IO流。

如果all_one为真,则将每个对象的尺寸报告为1,以便可以轻松计数。否则,报告实际尺寸。

源代码

Profile中的方法未导出,需要例如以Profile.take_heap_snapshot()的方式调用。

julia> using Profile

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

跟踪并记录堆上的 Julia 对象。这仅记录 Julia 垃圾回收器已知的对象。外部库分配的且垃圾回收器未管理的内存不会显示在快照中。

生成的堆快照文件可以上传到 chrome devtools 以进行查看。有关更多信息,请参阅chrome devtools 文档