资料分析
该 个人资料 模块提供了帮助开发人员提高代码性能的工具。 使用时,它会对正在运行的代码进行测量,并生成输出,帮助您了解在单个行上花费了多少时间。 最常见的用法是将"瓶颈"识别为优化目标。
个人资料 实现所谓的"采样"或https://en.wikipedia.org/wiki/Profiling_(computer_programming)[统计分析器]。 它的工作原理是在执行任何任务期间定期采取回溯。 每个回溯捕获当前运行的函数和行号,以及导致此行的完整函数调用链,因此是当前执行状态的"快照"。
如果大部分运行时间用于执行特定的代码行,则此行将经常显示在所有回溯集中。 换句话说,给定行的"成本"-或者实际上,函数调用序列到并包括这一行的成本-与它在所有回溯集合中出现的频率成正比。
采样分析器不提供完整的逐行复盖,因为回溯是间隔发生的(默认情况下,Unix系统上为1毫秒,Windows上为10毫秒,尽管实际调度受操作系统负载的影响)。 而且,如下面进一步讨论的,因为在所有执行点的稀疏子集处收集样本,所以由一个采样探查器收集的数据受到统计噪声的影响。
尽管存在这些限制,采样分析器仍具有很大的优势:
*您无需对代码进行任何修改即可进行时序测量。 *它可以配置到Julia的核心代码中,甚至(可选)到C和Fortran库中。 *通过"不经常"运行,性能开销很小;在分析时,您的代码可以以几乎本机的速度运行。
出于这些原因,建议您在考虑任何替代方案之前尝试使用内置的sampling profiler。
基本用法
让我们使用一个简单的测试用例:
julia> function myfunc()
A = rand(200, 200, 400)
maximum(A)
end
最好先运行你打算至少配置一次的代码(除非你想配置Julia的JIT编译器):
julia> myfunc() # run once to force compilation
现在我们已经准备好分析这个函数了:
julia> using Profile
julia> @profile myfunc()
要查看分析结果,有几个图形浏览器。 可视化器的一个"家族"基于https://github.com/timholy/FlameGraphs.jl[FlameGraphs.jl],每个家庭成员提供不同的用户界面:
*Vs Code是一个完整的IDE,内置对配置文件可视化的支持 *ProfileView.jl是一个基于GTK的独立可视化工具 *ProfileVega.jl使用VegaLight并与Jupyter笔记本很好地集成 *StatProfilerHTML.jl生成HTML并提供一些额外的摘要,并且还与Jupyter笔记本很好地集成 *ProfileSVG.jl渲染SVG *PProf.jl服务于一个本地网站,用于检查图表、火焰图和更多 *ProfileCanvas.jl是一个基于html画布的配置文件查看器UI,由https://www.julia-vscode.org/[Julia VS Code extension],但也可以生成交互式HTML文件。
一个完全独立的配置文件可视化方法是https://github.com/vchuravy/PProf.jl[PProf.jl],它使用外部 pprof 工具。
不过,在这里,我们将使用标准库附带的基于文本的显示:
julia> Profile.print()
80 ./event.jl:73; (::Base.REPL.##1#2{Base.REPL.REPLBackend})()
80 ./REPL.jl:97; macro expansion
80 ./REPL.jl:66; eval_user_input(::Any, ::Base.REPL.REPLBackend)
80 ./boot.jl:235; eval(::Module, ::Any)
80 ./<missing>:?; anonymous
80 ./profile.jl:23; macro expansion
52 ./REPL[1]:2; myfunc()
38 ./random.jl:431; rand!(::MersenneTwister, ::Array{Float64,3}, ::Int64, ::Type{B...
38 ./dSFMT.jl:84; dsfmt_fill_array_close_open!(::Base.dSFMT.DSFMT_state, ::Ptr{F...
14 ./random.jl:278; rand
14 ./random.jl:277; rand
14 ./random.jl:366; rand
14 ./random.jl:369; rand
28 ./REPL[1]:3; myfunc()
28 ./reduce.jl:270; _mapreduce(::Base.#identity, ::Base.#scalarmax, ::IndexLinear,...
3 ./reduce.jl:426; mapreduce_impl(::Base.#identity, ::Base.#scalarmax, ::Array{F...
25 ./reduce.jl:428; mapreduce_impl(::Base.#identity, ::Base.#scalarmax, ::Array{F...
此显示的每一行表示代码中的特定点(行号)。 缩进用于指示函数调用的嵌套序列,更多缩进的行在调用序列中更深。 在每一行中,第一个"字段"是从这一行或在这一行执行的任何函数中提取的回溯(样本)的数量。 第二个字段是文件名和行号,第三个字段是函数名。 请注意,特定的行号可能会随着Julia代码的变化而变化;如果你想跟随,最好自己运行这个例子。
在这个例子中,我们可以看到调用的顶层函数在文件中 事件。jl. 这是在启动Julia时运行REPL的函数。 如果你检查第97行 REPL。jl,你会看到这是功能的地方 eval_user_input() 被调用。 这是一个计算你在REPL输入的内容的函数,因为我们正在交互地工作,所以当我们输入这些函数时就被调用了 @个人资料myfunc(). 下一行反映了在 @个人资料宏。
第一行显示80个回溯是在 事件。jl,但这并不是说这条线路本身"昂贵":第三条线路揭示了所有80条回溯实际上都是在它的调用中触发的。 eval_user_input,等等。 要找出哪些操作实际上占用了时间,我们需要深入研究调用链。
此输出中的第一个"重要"行是这一行:
52 ./REPL[1]:2; myfunc()
代表,代表 指的是我们定义的事实 我的基金 在REPL中,而不是把它放在一个文件中;如果我们使用了一个文件,这将显示文件名。 该 [1] 显示功能 我的基金 是此REPL会话中评估的第一个表达式。 第2行 myfunc() 包含调用 兰德,并且有52个(80个)回溯发生在这条线上。 在下面,你可以看到一个调用 dsfmt_fill_array_close_open! 里面 dSFMT。jl.
再往下一点,你看:
28 ./REPL[1]:3; myfunc()
第3行 我的基金 包含调用 最大值,这里有28个(80个)回溯。 下面,你可以看到具体的地方 基/减少。jl 在 最大值 这种类型的输入数据的功能。
总体而言,我们可以初步得出结论,生成随机数的成本大约是找到最大元素的两倍。 我们可以通过收集更多的样品来增加我们对这一结果的信心:
julia> @profile (for i = 1:100; myfunc(); end)
朱莉娅>个人资料.印刷()
[....]
3821 ./REPL[1]:2;myfunc()
3511 ./随机的。jl:431;兰德!(::MersenneTwister,::数组{Float64,3},::Int64,::类型。..
3511 ./dSFMT。jl:84;dsfmt_fill_array_close_open!(::基地。dSFMT。DSFMT_state,::Ptr。..
310 ./随机的。jl:278;兰德
[....]
2893 ./REPL[1]:3;myfunc()
2893 ./减少。jl:270;_mapreduce(::Base.#identity,::Base。#scalarmax,::IndexLinea...
[....]
一般来说,如果你有 N 在一条线上收集的样品,您可以预期在订单上的不确定性 sqrt(N) (不包括其他噪音来源,如计算机与其他任务的繁忙程度)。 此规则的主要例外是垃圾收集,它不经常运行,但往往非常昂贵。 (由于Julia的垃圾收集器是用C编写的,因此可以使用 C=真 输出模式如下所述,或通过使用https://github.com/timholy/ProfileView.jl[ProfileView.jl]。)
这说明了默认的"树"转储;另一种选择是"平面"转储,它累积计数独立于它们的嵌套:
julia> Profile.print(format=:flat)
Count File Line Function
6714 ./<missing> -1 anonymous
6714 ./REPL.jl 66 eval_user_input(::Any, ::Base.REPL.REPLBackend)
6714 ./REPL.jl 97 macro expansion
3821 ./REPL[1] 2 myfunc()
2893 ./REPL[1] 3 myfunc()
6714 ./REPL[7] 1 macro expansion
6714 ./boot.jl 235 eval(::Module, ::Any)
3511 ./dSFMT.jl 84 dsfmt_fill_array_close_open!(::Base.dSFMT.DSFMT_s...
6714 ./event.jl 73 (::Base.REPL.##1#2{Base.REPL.REPLBackend})()
6714 ./profile.jl 23 macro expansion
3511 ./random.jl 431 rand!(::MersenneTwister, ::Array{Float64,3}, ::In...
310 ./random.jl 277 rand
310 ./random.jl 278 rand
310 ./random.jl 366 rand
310 ./random.jl 369 rand
2893 ./reduce.jl 270 _mapreduce(::Base.#identity, ::Base.#scalarmax, :...
5 ./reduce.jl 420 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
253 ./reduce.jl 426 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
2592 ./reduce.jl 428 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
43 ./reduce.jl 429 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
如果您的代码具有递归,那么一个可能令人困惑的问题是,"子"函数中的一行可以累积比总回溯更多的计数。 考虑以下函数定义:
dumbsum(n::Integer) = n == 1 ? 1 : 1 + dumbsum(n-1)
dumbsum3() = dumbsum(3)
如果你要做个人资料 哑弹3,并且在执行时采取了回溯 哑弹(1),后路会是这样的:
dumbsum3
dumbsum(3)
dumbsum(2)
dumbsum(1)
因此,这个子函数得到3个计数,即使父函数只得到一个。 "树"表示使得这一点更加清晰,因此(以及其他原因)可能是查看结果的最有用的方式。
用于控制配置文件结果显示的选项
个人资料。印刷业比我们到目前为止描述的更多的选择。 让我们看看完整的声明:
function print(io::IO = stdout, data = fetch(); kwargs...)
让我们先讨论两个位置参数,然后讨论关键字参数:
* 伊俄 --允许您将结果保存到缓冲区,例如文件,但默认是打印到 标准输出 (控制台)。
* 数据资料 --包含您要分析的数据;默认情况下,从 个人资料。取(),它从预先分配的缓冲区中拉出回溯。 例如,如果你想分析探查器,你可以说:
+
data = copy(Profile.fetch())
Profile.clear()
@profile Profile.print(stdout, data) # Prints the previous results
Profile.print() # Prints results from Profile.print()
关键字参数可以是:
* 格式 --上面介绍,确定回溯是否打印(默认, :树)或没有(:平)表示树结构的缩进。
* C --如果 真的,显示了C和Fortran代码的回溯(通常它们被排除在外)。 尝试运行介绍性示例 个人资料。打印(C=true). 这对于决定导致瓶颈的是Julia代码还是C代码非常有帮助;设置 C=真 还提高了嵌套的可解释性,但代价是更长的配置文件转储。
* 联合收割机 --某些代码行包含多个操作;例如, s+=A[i] 同时包含数组引用(A[i])和求和运算。 这些对应于生成的机器代码中的不同行,并且因此在该行上的回溯期间可能有两个或更多个不同的地址捕获。 合并=真 将它们组合在一起,并且可能是您通常想要的,但是您可以为每个唯一的指令指针单独生成一个输出 合并=错误.
* 最大深度 --限制帧的深度高于 最大深度 在 :树 格式。
* 索特比 --控制订单 :平 格式。 :filefuncline (默认)按源行排序,而 :计数 按采集样本数量的顺序排序。
* 噪音地板 --限制低于样本启发式噪底的帧(仅适用于格式 :树). 为此尝试的建议值为2.0(默认值为0)。 此参数隐藏其样本 n<=noisefloor*√N,在哪里 n 是这条线上的样本数量,并且 N 是被调用方的样本数。
* 最小人数 --限制少于 最小人数 发生。
文件/函数名有时会被截断( ...),并且缩进用a截断 +n 在开始的时候,在哪里 n 是如果有空间的话,会被插入的额外空间的数量。 如果您想要深度嵌套代码的完整配置文件,通常一个好主意是使用宽 显示尺寸 在一个 IOContext:
open("/tmp/prof.txt", "w") do s
Profile.print(IOContext(s, :displaysize => (24, 500)))
end
配置
@个人资料只是累积回溯,当你打电话时分析就会发生 个人资料。印刷(). 对于长时间运行的计算,完全可能会填充用于存储回溯的预分配缓冲区。 如果发生这种情况,回溯将停止,但您的计算仍在继续。 因此,您可能会错过一些重要的分析数据(发生这种情况时会收到警告)。
您可以通过这种方式获取和配置相关参数:
Profile.init() # returns the current settings
Profile.init(n = 10^7, delay = 0.01)
n 是您可以存储的指令指针总数,默认值为 10^6. 如果您的典型回溯是20个指令指针,那么您可以收集50000个回溯,这表明统计不确定性小于1%。 对于大多数应用来说,这可能已经足够好了。
因此,您更有可能需要修改 延迟,以秒表示,它设置Julia在快照之间执行请求的计算所需的时间。 长时间运行的作业可能不需要频繁回溯。 默认设置为 延迟=0.001. 当然,您既可以减少延迟,也可以增加延迟;但是,一旦延迟与回溯所需的时间量相似(作者的笔记本电脑上的~30微秒),分析的开销就会增加。
墙时间分析器
介绍和问题动机
上一节中描述的分析器是一个采样CPU分析器。 在高级别上,探查器会定期停止所有Julia计算线程以收集它们的回溯,并根据包含来自该函数的帧的回溯样本的数量来估计在每个函数中花费的时间。 但是,请注意,只有在探查器停止它们之前当前在系统线程上运行的任务才会收集它们的回溯。
虽然此探查器通常非常适合大多数任务都是计算绑定的工作负载,但对于大多数任务都是IO繁重的系统或诊断代码中同步原语的争用,它的帮助较小。
让我们考虑一下这个简单的工作量:
using Base.Threads
using Profile
using PProf
ch=通道(1)
const N_SPAWNED_TASKS=(1<<10)
const WAIT_TIME_NS=10_000_000
函数spawn_a_bunch_of_tasks_waiting_on_channel()
对于i in1:N_SPAWNED_TASKS
线程。@产卵开始
拿!(ch)
结束
结束
结束
功能busywait()
t0=time_ns()
虽然真实
如果time_ns()-t0>WAIT_TIME_NS
休息
结束
结束
结束
功能主要()
spawn_a_bunch_of_tasks_waiting_on_channel()
对于i in1:N_SPAWNED_TASKS
放!(ch,i)
布西威特()
结束
结束
个人资料。@个人资料主要()
我们的目标是检测是否存在争用 ch 通道—即,在通道中生产工作项的速度下,服务员的数量是否过多。
此配置文件不提供任何信息来帮助确定系统同步原语中发生争用的位置。 通道上的服务员将被阻塞和取消调度,这意味着没有系统线程将运行分配给这些服务员的任务,因此,他们将不会被分析器采样。
墙时间分析器
而不是对线程进行采样-因此只对正在运行的任务进行采样-wall-time task profiler独立于其调度状态对任务进行采样。 例如,在探查器运行时在同步原语上休眠的任务将以与探查器试图捕获回溯时主动运行的任务相同的概率进行采样。
这种方法允许我们构建一个配置文件,从阻塞的任务中回溯 ch 通道,如上面的例子,实际上表示。
让我们运行相同的示例,但现在使用wall-time分析器:
using Base.Threads
using Profile
using PProf
ch = Channel(1)
const N_SPAWNED_TASKS = (1 << 10)
const WAIT_TIME_NS = 10_000_000
function spawn_a_bunch_of_tasks_waiting_on_channel()
for i in 1:N_SPAWNED_TASKS
Threads.@spawn begin
take!(ch)
end
end
end
功能busywait()
t0=time_ns()
虽然真实
如果time_ns()-t0>WAIT_TIME_NS
休息
结束
结束
结束
功能主要()
spawn_a_bunch_of_tasks_waiting_on_channel()
对于i in1:N_SPAWNED_TASKS
放!(ch,i)
布西威特()
结束
结束
个人资料。@profile_walltime主()
我们看到大量样本来自与通道相关的 拿! 功能,这使我们能够确定确实有过多的服务员在 ch.
受计算限制的工作负载
尽管wall-time profiler对系统中的所有实时任务(而不仅仅是当前正在运行的任务)进行采样,但它仍然有助于识别性能热点,即使您的代码是计算绑定的。 让我们考虑一个简单的例子:
using Base.Threads
using Profile
using PProf
ch = Channel(1)
const MAX_ITERS = (1 << 22)
const N_TASKS = (1 << 12)
function spawn_a_task_waiting_on_channel()
Threads.@spawn begin
take!(ch)
end
end
function sum_of_sqrt()
sum_of_sqrt = 0.0
for i in 1:MAX_ITERS
sum_of_sqrt += sqrt(i)
end
return sum_of_sqrt
end
function spawn_a_bunch_of_compute_heavy_tasks()
Threads.@sync begin
for i in 1:N_TASKS
Threads.@spawn begin
sum_of_sqrt()
end
end
end
end
function main()
spawn_a_task_waiting_on_channel()
spawn_a_bunch_of_compute_heavy_tasks()
end
Profile.@profile_walltime main()
注意有多少样本包含 sum_of_sqrt,这是我们示例中昂贵的计算函数。
识别配置文件中的任务采样失败
在当前实现中,wall-time profiler尝试从上次垃圾回收以来一直存在的任务以及之后创建的任务中进行采样。 但是,如果大多数任务都非常短暂,则最终可能会对已经完成的任务进行采样,从而导致错过回溯捕获。
如果遇到含有 failed_to_sample_task_fun 或 failed_to_stop_thread_fun,这可能表明大量的短期任务,这阻止了他们的回溯被收集。
让我们考虑这个简单的例子:
using Base.Threads
using Profile
using PProf
const N_SPAWNED_TASKS = (1 << 16)
const WAIT_TIME_NS = 100_000
function spawn_a_bunch_of_short_lived_tasks()
for i in 1:N_SPAWNED_TASKS
Threads.@spawn begin
# Do nothing
end
end
end
function busywait()
t0 = time_ns()
while true
if time_ns() - t0 > WAIT_TIME_NS
break
end
end
end
function main()
GC.enable(false)
spawn_a_bunch_of_short_lived_tasks()
for i in 1:N_SPAWNED_TASKS
busywait()
end
GC.enable(true)
end
Profile.@profile_walltime main()
请注意,生成的任务 spawn_a_bunch_of_short_lived_tasks 是极其短暂的。 由于这些任务在系统中占大多数,我们可能会错过捕获大多数采样任务的回溯。
大量样本来自 failed_to_stop_thread_fun 确认我们在系统中有大量的短暂任务。
内存分配分析
提高性能的最常见技术之一是减少内存分配。 Julia提供了几种工具来衡量这一点:
GC日志记录
而 @时间记录有关内存使用情况和垃圾收集的高级统计信息在计算表达式的过程中,记录每个垃圾收集事件非常有用,可以直观地了解垃圾收集器运行的频率、每次运行的时间以及每次收集的垃圾量。 这可以通过以下方式启用 GC。enable_logging(真),这导致Julia每次发生垃圾回收时都会登录到stderr。
分配分析器
|
兼容性
Julia1.8此功能至少需要Julia1.8。 |
分配探查器在运行时记录每个分配的堆栈跟踪、类型和大小。 它可以用 个人资料。分配。@个人资料.
有关分配的此信息作为 分配数目 物体,包裹在一个 分配结果 对象。 可视化这些的最好方法是目前与https://github.com/JuliaPerf/PProf.jl[PProf.jl]和https://github.com/pfitzseb/ProfileCanvas.jl[ProfileCanvas.jl]包,它可以可视化进行最多分配的调用堆栈。
分配探查器确实有很大的开销,所以 样本/样本 参数可以通过跳过一些分配来加速它。 传球 sample_rate=1.0 会让它记录一切(这是缓慢的); sample_rate=0.1 将只记录10%的分配(更快)等。
|
兼容性
Julia1.11Julia的旧版本无法在所有情况下捕获类型。 在Julia的旧版本中,如果您看到类型的分配 |
自Julia1.11以来,所有分配都应该报告一个类型。
有关如何使用此工具的更多详细信息,请参阅JuliaCon2022的以下演讲:https://www.youtube.com/watch?v=BFvpwC8hEWQ
分配分析器示例
在这个简单的例子中,我们使用PProf来可视化分配配置文件。 您可以改用其他可视化工具。 我们收集配置文件(指定采样率),然后将其可视化。
using Profile, PProf
Profile.Allocs.clear()
Profile.Allocs.@profile sample_rate=0.0001 my_function()
PProf.Allocs.pprof()
下面是一个更深入的例子,展示了我们如何调整采样率。 大量的样品的目标是大约1-10万. 太多,配置文件可视化工具可能会不堪重负,并且分析会很慢。 太少,而且你没有代表性的样本。
julia> import Profile
julia> @time my_function() # Estimate allocations from a (second-run) of the function
0.110018 seconds (1.50 M allocations: 58.725 MiB, 17.17% gc time)
500000
julia> Profile.Allocs.clear()
朱莉娅>个人资料.分配。@profile sample_rate=0.001begin#1.5M*0.001=~1.5K allocs。
my_函数()
结束
500000
朱莉娅>教授=配置文件.分配。fetch();#如果你愿意,你也可以手动检查结果。
julia>length(prof.allocs)#确认我们有预期的分配数量。
1515
julia>现在使用PProf#,使用外部工具(如PProf或ProfileCanvas)进行可视化。
朱莉娅>PProf。分配。pprof(prof;from_c=false)#您可以选择传入先前获取的配置文件结果。
分析1515个分配样本。.. 100%|████████████████████████████████| 时间:0:00:00
主二进制文件名不可用.
在http://localhost:62261
"alloc-profile。pb。gz"
然后,您可以通过导航到http://localhost:62261,并将配置文件保存到磁盘。 有关更多选项,请参阅PProf软件包。
分配分析提示
如上所述,在您的个人资料中瞄准大约1-10千个样本。
请注意,我们在_all allocations_的空间中统一采样,并且没有通过分配的大小来加权我们的样本。 因此,给定的分配配置文件可能不会给出大多数字节在程序中分配的代表性配置文件,除非您设置了 sample_rate=1.
分配可以来自用户直接构造对象,但也可以来自运行时内部或插入到编译代码中以处理类型不稳定性。 查看"源代码"视图可能有助于隔离它们,然后是其他外部工具,例如https://github.com/JuliaDebug/Cthulhu.jl[脧锚脧赂`克苏鲁。jl`]可用于识别分配的原因。
分配配置文件可视化工具
现在有几个概要分析可视化工具可以全部显示分配概要文件。 以下是我们所知道的一些主要的列表:
*PProf.jl
*ProfileCanvas.jl
*VSCode的内置配置文件可视化工具(@profview_allocs)[需要文件]
*直接在REPL中查看结果
**您可以通过REPL检查结果 个人资料。分配。取(),以查看每个分配的stacktrace和类型。
逐行分配跟踪
衡量分配的另一种方法是从Julia开始 --轨道分配=<设置> 命令行选项,您可以选择 无 (默认情况下,不测量分配), 用户 (除了Julia的核心代码之外,在任何地方测量内存分配),或者 全部 (在Julia代码的每一行测量内存分配)。 分配为每行编译代码进行测量。 当您退出Julia时,累积结果将写入具有 .梅姆 附加在文件名之后,与源文件位于同一目录中。 每行列出分配的总字节数。 该https://github.com/JuliaCI/Coverage.jl[脧锚脧赂`保障范围` 包]包含一些基本的分析工具,例如按分配的字节数对行进行排序。
在解释结果时,有一些重要的细节。 在 用户 设置,任何直接从REPL调用的函数的第一行都会由于REPL代码本身发生的事件而表现出分配。 更重要的是,JIT-compilation还增加了分配计数,因为Julia的大部分编译器都是用Julia编写的(并且编译通常需要内存分配)。 推荐的过程是通过执行所有要分析的命令来强制编译,然后调用 个人资料。clear_malloc_data()重置所有分配计数器。 最后,执行所需的命令并退出Julia以触发生成 .梅姆 文件。
|
注 |
外部分析
目前Julia支持 英特尔VTune, 文件/文件 和 佩尔夫 作为外部分析工具。
根据您选择的工具,使用 使用_INTEL_JITEVENTS, USE_OPROFILE_JITEVENTS 和 使用_PERF_JITEVENTS 设置为1in 做吧。用户. 支持多个标志。
在运行之前,设置环境变量 ENABLE_JITPROFILING至1。
现在你有很多方法来使用这些工具! 例如与 文件/文件 你可以尝试一个简单的录音 :
>ENABLE_JITPROFILING=1 sudo operf -Vdebug ./julia test/fastmath.jl >opreport -l `which ./julia`
或类似地与 佩尔夫 :
$ENABLE_JITPROFILING=1perf记录-o/tmp/perf。data--call-graph dwarf-k1。/julia/test/fastmath。jl $perf inject--jit--input/tmp/perf。数据--输出/tmp/perf-jit。数据资料 $perf报告--call-graph-G-i/tmp/perf-jit。数据资料
还有更多有趣的事情,你可以衡量你的程序,以获得一个全面的列表,请阅读https://www.brendangregg.com/perf.html[Linux perf示例页面]。
请记住,perf为每个执行保存a 佩尔夫。数据资料 即使对于小程序,文件也会变得相当大。 此外,perf LLVM模块将临时调试对象保存在 ~/.调试/jit,记得经常清理该文件夹。