AnyMath 文档

手册

该页面正在翻译中。

BenchmarkTools的创建是为了促进以下任务:

  1. 将基准集合组织到可管理的基准套件中

  2. 配置、保存和重新加载基准参数,以方便、准确和一致

  3. 以产生合理和一致的性能预测的方式执行基准

  4. 分析和比较结果,以确定代码更改是否导致回归或改进

在我们走得太远之前,让我们定义本文档中使用的一些术语:

*"评估":基准表达式的单次执行。 *"样本":通过运行多个评估获得的单个时间/内存测量。 *"试验":收集多个样品的实验(或这种实验的结果)。 *"基准参数":决定如何执行基准测试的配置设置

我们对"样本"定义背后的推理可能并不是所有读者都很明显。 如果执行基准的时间小于您的计时方法的分辨率,那么对基准的单个评估通常不会产生有效样本。 在这种情况下,必须通过记录总时间来近似有效样本 t 需要记录 n 评估,并估计样本每次评估的时间为 t/n. 例如,如果样本进行100万次评估需要1秒,则该样本每次评估的大约时间为1微秒。 对于任何给定的基准,每个样本的正确评估数量并不明显,所以BenchmarkTools提供了一个机制( 调子! 方法)自动为您弄清楚。

基准测试基础

定义和执行基准

要快速基准Julia表达式,请使用 @基准:

julia> @benchmark sin(1)
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  1.442 ns … 53.028 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     1.453 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   1.462 ns ±  0.566 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

                                   █
  ▂▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃▁▁▃
  1.44 ns           Histogram: frequency by time           1.46 ns (top 1%)

 Memory estimate: 0 bytes, allocs estimate: 0.

@基准 宏本质上是定义基准、自动调整基准配置参数和运行基准的简写。 这三个步骤可以明确地使用 @benchmarkable, 调子!:

julia> b = @benchmarkable sin(1); # define the benchmark with default parameters

# find the right evals/sample and number of samples to take for this benchmark
julia> tune!(b);

朱莉娅>运行(b)
BenchmarkTools.试验:10000个样品,1000个评价.
 量程(最小...最大):1.442ns...4.308ns÷GC(最小...最大):0.00%...0.00%
 时间(中位数):1.453ns÷GC(中位数):0.00%
 时间(均值±σ):1.456ns±0.056ns÷GC(均值±σ):0.00%±0.00%

                                  █
  ▂▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂▁▁▃
  1.44ns直方图:按时间划分的频率1.46ns(前1%)

 内存估计:0字节,allocs估计:0。

或者,您可以使用 @btime, @btimed, @belapsed, @ballocated,或 @气球 宏。 这些论点与…​…​完全相同 @基准,但表现得像 @时间, @定时, @经过, @分配,或 @分配 Julia附带的宏。

julia> @btime sin(1)
  13.612 ns (0 allocations: 0 bytes)
0.8414709848078965

julia> @belapsed sin(1)
1.3614228456913828e-8

julia> @btimed sin(1)
(value = 0.8414709848078965, time = 9.16e-10, bytes = 0, alloc = 0, gctime = 0.0)

julia> @ballocated rand(4, 4)
208

julia> @ballocations rand(4, 4)
2

基准 参数

您可以将以下关键字参数传递给 @基准, @benchmarkable,而 配置执行过程:

* 样本:要采取的样本数量。 如果收集了这么多样本,执行将结束。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。样本=10000. * 秒数:基准测试过程的预算秒数。 如果超过这个时间,审判将终止(无论 样本),但至少会一直采取一个样本。 实际上,实际运行时间可能会超出样本持续时间的预算。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。秒=5. * 逃避,逃避:每个样本的评价次数。 为了获得最佳结果,这应该在试验之间保持一致。 这个值的一个很好的猜测可以通过 调子!,但使用 调子! 可以比设置不太一致 逃避,逃避 手动(绕过调谐)。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。evals=1. 如果你研究的函数改变了它的输入,那么设置它可能是一个好主意 evals=1 手动。 * 开销:每次评估的估计循环开销,以纳秒为单位,自动从每次采样时间测量中减去。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。开销=0. BenchmarkTools.估计值/估计值 可以调用以凭经验确定该值(然后可以将其设置为默认值,如果需要的话)。 * [医]气相色谱:如果 真的,跑 gc() 在执行这个基准测试之前。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。gctrial=真. * gcsample:如果 真的,跑 gc() 每个样品之前。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。gcsample=false. * 时间-时间:基准时间估计的噪声容限,以百分比表示。 这是在基准执行后,分析结果时使用的。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。time_tolerance=0.05. * 记忆-记忆:基准内存估计的噪声容限,以百分比表示。 这是在基准执行后,分析结果时使用的。 默认值为 BenchmarkTools.DEFAULT_PARAMETERS。memory_tolerance=0.01.

要更改上述字段的默认值,可以更改 BenchmarkTools.默认参数,例如:

# change default for `seconds` to 2.5
BenchmarkTools.DEFAULT_PARAMETERS.seconds = 2.50
# change default for `time_tolerance` to 0.20
BenchmarkTools.DEFAULT_PARAMETERS.time_tolerance = 0.20

下面是一个演示如何将这些参数传递给基准定义的示例:

b = @benchmarkable sin(1) seconds=1 time_tolerance=0.01
run(b) # equivalent to run(b, seconds = 1, time_tolerance = 0.01)

将值插入基准表达式

您可以将值插值到 @基准@benchmarkable 表达方式:

# rand(1000) is executed for each evaluation
julia> @benchmark sum(rand(1000))
BenchmarkTools.Trial: 10000 samples with 10 evaluations.
 Range (min … max):  1.153 μs … 142.253 μs  ┊ GC (min … max): 0.00% … 96.43%
 Time  (median):     1.363 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   1.786 μs ±   4.612 μs  ┊ GC (mean ± σ):  9.58% ±  3.70%

   ▄▆██▇▇▆▄▃▂▁                           ▁▁▂▂▂▂▂▂▂▁▂▁
  ████████████████▆▆▇▅▆▇▆▆▆▇▆▇▆▆▅▄▄▄▅▃▄▇██████████████▇▇▇▇▆▆▇▆▆▅▅▅▅
  1.15 μs         Histogram: log(frequency) by time          3.8 μs (top 1%)

 Memory estimate: 7.94 KiB, allocs estimate: 1.

# rand(1000) is evaluated at definition time, and the resulting
# value is interpolated into the benchmark expression
julia> @benchmark sum($(rand(1000)))
BenchmarkTools.Trial: 10000 samples with 963 evaluations.
 Range (min … max):  84.477 ns … 241.602 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     84.497 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   85.125 ns ±   5.262 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █
  █▅▇▅▄███▇▇▆▆▆▄▄▅▅▄▄▅▄▄▅▄▄▄▄▁▃▄▁▁▃▃▃▄▃▁▃▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▃▃▁▁▁▃▁▁▁▁▆
  84.5 ns         Histogram: log(frequency) by time           109 ns (top 1%)

 Memory estimate: 0 bytes, allocs estimate: 0.

一个好的经验法则是*外部变量应该显式插值到基准表达式中*:

julia> A = rand(1000);

# BAD: A is a global variable in the benchmarking context
julia> @benchmark [i*i for i in A]
BenchmarkTools.Trial: 10000 samples with 54 evaluations.
 Range (min … max):  889.241 ns … 29.584 μs  ┊ GC (min … max):  0.00% … 93.33%
 Time  (median):       1.073 μs              ┊ GC (median):     0.00%
 Time  (mean ± σ):     1.296 μs ±  2.004 μs  ┊ GC (mean ± σ):  14.31% ±  8.76%

      ▃█▆
  ▂▂▄▆███▇▄▄▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▁▂▂▁▁▁▁▁▂▁▁▁▁▂▂▁▁▁▁▂▁▁▁▁▁▁▂▂▂▂▂▂▂▂▂▂
  889 ns             Histogram: frequency by time            2.92 μs (top 1%)

 Memory estimate: 7.95 KiB, allocs estimate: 2.

# GOOD: A is a constant value in the benchmarking context
julia> @benchmark [i*i for i in $A]
BenchmarkTools.Trial: 10000 samples with 121 evaluations.
 Range (min … max):  742.455 ns … 11.846 μs  ┊ GC (min … max):  0.00% … 88.05%
 Time  (median):     909.959 ns              ┊ GC (median):     0.00%
 Time  (mean ± σ):     1.135 μs ±  1.366 μs  ┊ GC (mean ± σ):  16.94% ± 12.58%

  ▇█▅▂                                                             ▁
  ████▇▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▅▆██
  742 ns          Histogram: log(frequency) by time          10.3 μs (top 1%)

 Memory estimate: 7.94 KiB, allocs estimate: 1.

(请注意,"KiB"是一个SI前缀https://en.wikipedia.org/wiki/Kibibyte[kibibyte]:1024字节。)

请记住,您可以从基准中更改外部状态:

julia> A = zeros(3);

 # each evaluation will modify A
julia> b = @benchmarkable fill!($A, rand());

julia> run(b, samples = 1);

julia> A
3-element Vector{Float64}:
 0.4615582142515109
 0.4615582142515109
 0.4615582142515109

julia> run(b, samples = 1);

julia> A
3-element Vector{Float64}:
 0.06373849439691504
 0.06373849439691504
 0.06373849439691504

通常,您不能在 @基准@benchmarkable,因为所有基准都是通过设计在顶层范围内定义的。 但是,您可以通过将局部变量插入基准表达式来解决此问题:

# will throw UndefVar error for `x`
julia> let x = 1
           @benchmark sin(x)
       end

# will work fine
julia> let x = 1
           @benchmark sin($x)
       end

安装和拆卸阶段

BenchmarkTools允许您通过 安装程序拆毁;拆毁 表达至 @基准@benchmarkable. 该 安装程序 表达式在样本执行之前被评估,而 拆毁;拆毁 expression在样本执行后才计算。 这里有一个例子,这种东西是有用的:

julia> x = rand(100000);

# For each sample, bind a variable `y` to a fresh copy of `x`. As you
# can see, `y` is accessible within the scope of the core expression.
julia> b = @benchmarkable sort!(y) setup=(y = copy($x))
Benchmark(evals=1, seconds=5.0, samples=10000)

julia> run(b)
BenchmarkTools.Trial: 819 samples with 1 evaluations.
 Range (min … max):  5.983 ms …  6.954 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.019 ms              ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.029 ms ± 46.222 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

        ▃▂▂▄█▄▂▃
  ▂▃▃▄▆▅████████▇▆▆▅▄▄▄▅▆▄▃▄▅▄▃▂▃▃▃▂▂▃▁▂▂▂▁▂▂▂▂▂▂▁▁▁▁▂▂▁▁▁▂▂▁▁▂▁▁▂
  5.98ms直方图:按时间划分的频率6.18ms(前1%)

 内存估计:0字节,allocs估计:0。

在上面的例子中,我们希望对Julia的就地排序方法进行基准测试。 如果没有设置阶段,我们必须为每个样本分配一个新的输入向量(这样分配时间会污染我们的结果),或者对每个样本使用相同的输入向量(这样所有样本,但第一个样本会对错误的东西进行基准测试-对已经排序的向量进行排序)。 设置阶段通过允许我们做一些核心表达式可以利用的工作来解决问题,而不会将这些工作错误地包含在我们的性能结果中。

请注意, 安装程序拆毁;拆毁 阶段是*为每个样本执行,而不是每个评估*。 因此,如果以下情况,上面的排序示例将不会产生预期的结果 evals/样本>1 (它会遭受与已经排序的向量进行基准测试相同的问题)。

如果您的设置涉及多个对象,则需要用分号分隔分配,如下所示:

julia> @btime x + y setup = (x=1; y=2)  # works
  1.238 ns (0 allocations: 0 bytes)
3

julia> @btime x + y setup = (x=1, y=2)  # errors
ERROR: UndefVarError: `x` not defined

这也解释了如果您不小心在设置中为单个参数添加了逗号,则会出现错误:

julia> @btime exp(x) setup = (x=1,)  # errors
ERROR: UndefVarError: `x` not defined

了解编译器优化

LLVM和Julia的编译器可以在 @benchmarkable 表情。 在某些情况下,这些优化可以完全避免计算,从而产生意外的"快速"基准。 例如,下面的表达式是非分配的:

julia> @benchmark (view(a, 1:2, 1:2); 1) setup=(a = rand(3, 3))
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  2.885 ns … 14.797 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     2.895 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   3.320 ns ±  0.909 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █             ▁   ▁ ▁▁▁                                     ▂▃▃▁
  █▁▁▇█▇▆█▇████████████████▇█▇█▇▇▇▇█▇█▇▅▅▄▁▁▁▁▄▃▁▃▃▁▄▃▁▄▁▃▅▅██████
  2.88 ns        Histogram: log(frequency) by time         5.79 ns (top 1%)

 Memory estimate: 0 bytes, allocs estimate: 0.0

但是请注意,这并不意味着 视图(a,1:2,1:2) 是非分配的:

julia> @benchmark view(a, 1:2, 1:2) setup=(a = rand(3, 3))
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  3.175 ns … 18.314 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     3.176 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   3.262 ns ±  0.882 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █
  █▁▂▁▁▁▂▁▂▁▂▁▁▂▁▁▂▂▂▂▂▂▁▁▂▁▁▂▁▁▁▂▂▁▁▁▂▁▂▂▁▂▁▁▂▂▂▁▂▂▂▂▂▂▂▂▂▂▂▁▂▂▁▂
  3.18ns直方图:按时间划分的频率4.78ns(前1%)

 内存估计:0字节,allocs估计:0.8

这里的关键点是,这两个基准测试不同的东西,即使它们的代码是相似的。 在第一个例子中,Julia能够优化。 视图(a,1:2,1:2) 因为它可以证明价值没有被返回, a 没有变异。 在第二个示例中,不执行优化,因为 视图(a,1:2,1:2) 是基准表达式的返回值。

BenchmarkTools将忠实地报告您提供给它的确切代码的性能,包括可能发生的任何编译器优化,以完全退出代码。 由您来设计实际执行您打算执行的代码的基准。

一个常见的地方julia的优化器可能会导致基准测试无法测量用户认为它正在测量的是简单的操作,其中所有值在编译时都是已知的。 假设你想测量两个整数相加所需的时间:

julia> a = 1; b = 2
2

julia> @btime $a + $b
  0.024 ns (0 allocations: 0 bytes)
3

在这种情况下,朱莉娅能够使用的属性 +(::Int,::Int) 知道它可以安全地取代 $a+$b3 在编译时。 我们可以通过引用和解引用插值变量来阻止优化器这样做

julia> @btime $(Ref(a))[] + $(Ref(b))[]
  1.277 ns (0 allocations: 0 bytes)
3

处理基准结果

BenchmarkTools提供了与基准测试结果相关的四种类型:

* 审判:存储基准试验期间收集的所有样品,以及试验的参数 * [医]试验性:用于总结一个单一的估计 审判 * [医]试验,试验:两者的比较 [医]试验性 * TrialJudgement碌录潞陆:a的字段的分类 [医]试验,试验 作为 不变性, 回归,或 改善工程

本节提供了有限数量的示例来演示这些类型。 有关支持的功能的完整列表,请参阅 参考文件

审判[医]试验性

运行基准会生成 审判 类型:

julia> t = @benchmark eigen(rand(10, 10))
BenchmarkTools.Trial: 10000 samples with 1 evaluations.
 Range (min … max):  26.549 μs …  1.503 ms  ┊ GC (min … max): 0.00% … 93.21%
 Time  (median):     30.818 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   31.777 μs ± 25.161 μs  ┊ GC (mean ± σ):  1.31% ±  1.63%

             ▂▃▅▆█▇▇▆▆▄▄▃▁▁
  ▁▁▁▁▁▁▂▃▄▆████████████████▆▆▅▅▄▄▃▃▃▂▂▂▂▂▂▁▂▁▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
  26.5µs直方图:按时间划分的频率41.3µs(前1%)

 内存估计:16.36KiB,allocs估计:19.

julia>dump(t)#以下是试用版中实际存储的内容
BenchmarkTools.审判
  Params:BenchmarkTools.参数
    秒数:Float64 5.0
    样本:Int6410000
    evals:Int641
    开销:Float64 0.0
    gctrial:Bool true
    gcsample:Bool false
    time_tolerance:Float64 0.05
    memory_tolerance:Float64 0.01
  时间:阵列{Float64}((10000,)) [26549.0, 26960.0, 27030.0, 27171.0, 27211.0, 27261.0, 27270.0, 27311.0, 27311.0, 27321.0 ... 55383.0, 55934.0, 58649.0, 62847.0, 68547.0, 75761.0, 247081.0, 1.421718e6,1.488322e6,1.50329e6]
  gctimes:数组{Float64}((10000,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ... 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.366184e6,1.389518e6,1.40116e6]
  内存:Int64 16752
  拨款:Int6419

正如你可以从上面看到的,几个不同的时间估计是漂亮的打印与 审判. 你可以自己计算这些估计值 最低限额, 最大值, 中位数, 平均,而 性病 功能(注意 中位数, 平均,而 性病 被重新导入 长凳,长凳统计数字):

julia> minimum(t)
BenchmarkTools.TrialEstimate:
  time:             26.549 μs
  gctime:           0.000 ns (0.00%)
  memory:           16.36 KiB
  allocs:           19

julia> maximum(t)
BenchmarkTools.TrialEstimate:
  time:             1.503 ms
  gctime:           1.401 ms (93.21%)
  memory:           16.36 KiB
  allocs:           19

julia> median(t)
BenchmarkTools.TrialEstimate:
  time:             30.818 μs
  gctime:           0.000 ns (0.00%)
  memory:           16.36 KiB
  allocs:           19

julia> mean(t)
BenchmarkTools.TrialEstimate:
  time:             31.777 μs
  gctime:           415.686 ns (1.31%)
  memory:           16.36 KiB
  allocs:           19

julia> std(t)
BenchmarkTools.TrialEstimate:
  time:             25.161 μs
  gctime:           23.999 μs (95.38%)
  memory:           16.36 KiB
  allocs:           19

我应该使用哪个估算器?

对于我们测试过的基准,时间分布总是正确的。 这种现象可以通过考虑影响基准测试过程的机器噪音在某种意义上本质上是积极的-没有真正的噪音来源会定期导致您的机器执行一系列指令_faster_比 根据基准噪声的表征,我们可以描述我们的估计器的行为:

*最小值是时间分布的位置参数的鲁棒估计量,不应被视为异常值 *中位数作为中心倾向的稳健度量,应该相对不受异常值的影响 *均值作为中心倾向的非鲁棒度量,通常会被异常值正偏斜 *最大值应被视为主要由噪声驱动的异常值,并且在基准测试之间可能会发生剧烈变化。

[医]试验,试验TrialJudgement碌录潞陆

BenchmarkTools提供一个 比率 用于比较两个值的函数:

julia> ratio(3, 2)
1.5

julia> ratio(1, 0)
Inf

朱莉娅>比率(0,1)
0.0

#a==b是特殊的-cased到1.0,以防止NaNs在这种情况下
朱莉娅>比率(0,0)
1.0

致电 比率 功能在两个 [医]试验性 实例比较它们的字段:

julia> using BenchmarkTools

julia> b = @benchmarkable eigen(rand(10, 10));

julia> tune!(b);

julia> m1 = median(run(b))
BenchmarkTools.TrialEstimate:
  time:             38.638 μs
  gctime:           0.000 ns (0.00%)
  memory:           9.30 KiB
  allocs:           28

julia> m2 = median(run(b))
BenchmarkTools.TrialEstimate:
  time:             38.723 μs
  gctime:           0.000 ns (0.00%)
  memory:           9.30 KiB
  allocs:           28

julia> ratio(m1, m2)
BenchmarkTools.TrialRatio:
  time:             0.997792009916587
  gctime:           1.0
  memory:           1.0
  allocs:           1.0

使用 法官 决定作为第一个参数传递的估计值是否表示回归与第二个估计值的函数:

julia> m1 = median(@benchmark eigen(rand(10, 10)))
BenchmarkTools.TrialEstimate:
  time:             38.745 μs
  gctime:           0.000 ns (0.00%)
  memory:           9.30 KiB
  allocs:           28

julia> m2 = median(@benchmark eigen(rand(10, 10)))
BenchmarkTools.TrialEstimate:
  time:             38.611 μs
  gctime:           0.000 ns (0.00%)
  memory:           9.30 KiB
  allocs:           28

# percent change falls within noise tolerance for all fields
julia> judge(m1, m2)
BenchmarkTools.TrialJudgement:
  time:   +0.35% => invariant (5.00% tolerance)
  memory: +0.00% => invariant (1.00% tolerance)

# changing time_tolerance causes it to be marked as a regression
julia> judge(m1, m2; time_tolerance = 0.0001)
BenchmarkTools.TrialJudgement:
  time:   +0.35% => regression (0.01% tolerance)
  memory: +0.00% => invariant (1.00% tolerance)

# switch m1 & m2; from this perspective, the difference is an improvement
julia> judge(m2, m1; time_tolerance = 0.0001)
BenchmarkTools.TrialJudgement:
  time:   -0.35% => improvement (0.01% tolerance)
  memory: +0.00% => invariant (1.00% tolerance)

# you can pass in TrialRatios as well
julia> judge(ratio(m1, m2)) == judge(m1, m2)
true

请注意,GC时间和分配计数的更改不会按以下分类 法官. 这是因为GC时间和分配计数虽然有时对回答发生了回归很有用,但通常对回答发生了回归并不有用。 相反,通常只有时间和内存使用的差异才能确定代码更改是改进还是回归。 例如,在不太可能发生的情况下,代码更改减少了时间和内存使用,但增加了GC时间和分配计数,大多数人会认为代码更改是一种改进。 相反的情况也是如此:无论GC时间或分配计数减少多少,时间和内存使用的增加都将被视为回归。

Benchmark集团 类型

在现实世界中,人们经常处理整个基准套件,而不仅仅是单个基准。 该 Benchmark集团 类型作为此类套件的"组织单位",可用于存储和结构基准定义。 审判 数据、估算结果,甚至其他 Benchmark集团 实例。

定义基准套件

A Benchmark集团 商店a Dict,Dict 这将基准Id映射到值,以及可用于按主题过滤组的描述性"标签"。 首先,让我们演示如何使用 Benchmark集团 类型来定义一个简单的基准套件:

# Define a parent BenchmarkGroup to contain our suite
suite = BenchmarkGroup()

# Add some child groups to our benchmark suite. The most relevant BenchmarkGroup constructor
# for this case is BenchmarkGroup(tags::Vector). These tags are useful for
# filtering benchmarks by topic, which we'll cover in a later section.
suite["utf8"] = BenchmarkGroup(["string", "unicode"])
suite["trig"] = BenchmarkGroup(["math", "triangles"])

# Add some benchmarks to the "utf8" group
teststr = join(rand('a':'d', 10^4));
suite["utf8"]["replace"] = @benchmarkable replace($teststr, "a" => "b")
suite["utf8"]["join"] = @benchmarkable join($teststr, $teststr)

# Add some benchmarks to the "trig" group
for f in (sin, cos, tan)
    for x in (0.0, pi)
        suite["trig"][string(f), x] = @benchmarkable $(f)($x)
    end
end

让我们看看我们在REPL中新定义的套件:

julia> suite
2-element BenchmarkTools.BenchmarkGroup:
  tags: []
  "utf8" => 2-element BenchmarkTools.BenchmarkGroup:
	  tags: ["string", "unicode"]
	  "join" => Benchmark(evals=1, seconds=5.0, samples=10000)
	  "replace" => Benchmark(evals=1, seconds=5.0, samples=10000)
  "trig" => 6-element BenchmarkTools.BenchmarkGroup:
	  tags: ["math", "triangles"]
	  ("cos", 0.0) => Benchmark(evals=1, seconds=5.0, samples=10000)
	  ("sin", π = 3.1415926535897...) => Benchmark(evals=1, seconds=5.0, samples=10000)
	  ("tan", π = 3.1415926535897...) => Benchmark(evals=1, seconds=5.0, samples=10000)
	  ("cos", π = 3.1415926535897...) => Benchmark(evals=1, seconds=5.0, samples=10000)
	  ("sin", 0.0) => Benchmark(evals=1, seconds=5.0, samples=10000)
	  ("tan", 0.0) => Benchmark(evals=1, seconds=5.0, samples=10000)

正如你可能想象的那样, Benchmark集团 支持Julia的子集 联想,联想 界面。 可以找到这些受支持函数的完整列表 在参考文件中

也可以创建一个嵌套 Benchmark集团 只需通过索引键:

suite2 = BenchmarkGroup()

suite2["my"]["nested"]["benchmark"] = @benchmarkable sum(randn(32))

这将导致一个分层基准,而不需要我们创建 Benchmark集团 在每个级别上我们自己。

请注意,密钥是在访问时自动创建的,即使密钥不存在。 因此,如果您希望清空未使用的密钥,则可以使用 clear_empty!(套房) 这样做。

调整和运行 Benchmark集团

与个别基准类似,您可以 调子! 整个 Benchmark集团 实例(从上一节开始):

# execute `tune!` on every benchmark in `suite`
julia> tune!(suite);

# run with a time limit of ~1 second per benchmark
julia> results = run(suite, verbose = true, seconds = 1)
(1/2) benchmarking "utf8"...
  (1/2) benchmarking "join"...
  done (took 1.15406904 seconds)
  (2/2) benchmarking "replace"...
  done (took 0.47660775 seconds)
done (took 1.697970114 seconds)
(2/2) benchmarking "trig"...
  (1/6) benchmarking ("tan",π = 3.1415926535897...)...
  done (took 0.371586549 seconds)
  (2/6) benchmarking ("cos",0.0)...
  done (took 0.284178292 seconds)
  (3/6) benchmarking ("cos",π = 3.1415926535897...)...
  done (took 0.338527685 seconds)
  (4/6) benchmarking ("sin",π = 3.1415926535897...)...
  done (took 0.345329397 seconds)
  (5/6) benchmarking ("sin",0.0)...
  done (took 0.309887335 seconds)
  (6/6) benchmarking ("tan",0.0)...
  done (took 0.320894744 seconds)
done (took 2.022673065 seconds)
BenchmarkTools.BenchmarkGroup:
  tags: []
  "utf8" => BenchmarkGroup(["string", "unicode"])
  "trig" => BenchmarkGroup(["math", "triangles"])

在一个 Benchmark集团

从上一节开始,我们看到运行我们的基准套件返回一个 Benchmark集团 那商店 审判 数据而不是基准:

julia> results["utf8"]
BenchmarkTools.BenchmarkGroup:
  tags: ["string", "unicode"]
  "join" => Trial(133.84 ms) # summary(::Trial) displays the minimum time estimate
  "replace" => Trial(202.3 μs)

julia> results["trig"]
BenchmarkTools.BenchmarkGroup:
  tags: ["math", "triangles"]
  ("tan",π = 3.1415926535897...) => Trial(28.0 ns)
  ("cos",0.0) => Trial(6.0 ns)
  ("cos",π = 3.1415926535897...) => Trial(22.0 ns)
  ("sin",π = 3.1415926535897...) => Trial(21.0 ns)
  ("sin",0.0) => Trial(6.0 ns)
  ("tan",0.0) => Trial(6.0 ns)

结果相关类型上的大部分函数(审判, [医]试验性, [医]试验,试验,而 TrialJudgement碌录潞陆)工作 Benchmark集团s以及。 通常,这些函数只是映射到组的值上:

julia> m1 = median(results["utf8"]) # == median(results["utf8"])
BenchmarkTools.BenchmarkGroup:
  tags: ["string", "unicode"]
  "join" => TrialEstimate(143.68 ms)
  "replace" => TrialEstimate(203.24 μs)

julia> m2 = median(run(suite["utf8"]))
BenchmarkTools.BenchmarkGroup:
  tags: ["string", "unicode"]
  "join" => TrialEstimate(144.79 ms)
  "replace" => TrialEstimate(202.49 μs)

julia> judge(m1, m2; time_tolerance = 0.001) # use 0.1 % time tolerance
BenchmarkTools.BenchmarkGroup:
  tags: ["string", "unicode"]
  "join" => TrialJudgement(-0.76% => improvement)
  "replace" => TrialJudgement(+0.37% => regression)

索引到 Benchmark集团 使用 @标记

有时,特别是在大型基准测试套件中,您希望按主题过滤基准测试,而不必担心套件的键值结构。 例如,您可能希望运行所有与字符串相关的基准,即使它们可能分布在许多不同的组或子组中。 为了解决这个问题, Benchmark集团 类型包含一个标签系统.

考虑以下几点 Benchmark集团,其中包含多个嵌套的子组,这些子组都是单独标记的:

julia> g = BenchmarkGroup([], # no tags in the parent
                          "c" => BenchmarkGroup(["5", "6", "7"]), # tagged "5", "6", "7"
                          "b" => BenchmarkGroup(["3", "4", "5"]), # tagged "3", "4", "5"
                          "a" => BenchmarkGroup(["1", "2", "3"],  # contains tags and child groups
                                                "d" => BenchmarkGroup(["8"], 1 => 1),
                                                "e" => BenchmarkGroup(["9"], 2 => 2)));
julia> g
BenchmarkTools.BenchmarkGroup:
  tags: []
  "c" => BenchmarkTools.BenchmarkGroup:
	  tags: ["5", "6", "7"]
  "b" => BenchmarkTools.BenchmarkGroup:
	  tags: ["3", "4", "5"]
  "a" => BenchmarkTools.BenchmarkGroup:
	  tags: ["1", "2", "3"]
	  "e" => BenchmarkTools.BenchmarkGroup:
		  tags: ["9"]
		  2 => 2
	  "d" => BenchmarkTools.BenchmarkGroup:
		  tags: ["8"]
		  1 => 1

我们可以使用 @标记 宏。 此宏接受一个特殊的谓词,并返回一个可用于索引到 Benchmark集团. 例如,我们可以选择标记的所有组 "3""7" 而不是 "1":

julia> g[@tagged ("3" || "7") && !("1")]
BenchmarkTools.BenchmarkGroup:
  tags: []
  "c" => BenchmarkGroup(["5", "6", "7"])
  "b" => BenchmarkGroup(["3", "4", "5"])

正如你所看到的,允许的语法 @标记 谓词包括 !, (), ||, &&,除了标签本身。 该 @标记 宏将谓词表达式中的每个标记替换为检查组是否具有给定标记,返回 真的 如果是这样, 错误 否则。 一组 g 被认为具有给定的标记 t 如果:

* t 被明确地附加到 g 按建筑(例如 g=BenchmarkGroup([t])) * t 是一个关键点 gg母组(例如 基准组([],t=>g)) * t 是一个标签 g父组(一直到根组)

来演示最后两点:

# also could've used `@tagged "1"`, `@tagged "a"`, `@tagged "e" || "d"`
julia> g[@tagged "8" || "9"]
BenchmarkTools.BenchmarkGroup:
  tags: []
  "a" => BenchmarkTools.BenchmarkGroup:
	  tags: ["1", "2", "3"]
	  "e" => BenchmarkTools.BenchmarkGroup:
		  tags: ["9"]
		  2 => 2
	  "d" => BenchmarkTools.BenchmarkGroup:
		  tags: ["8"]
		  1 => 1

julia> g[@tagged "d"]
BenchmarkTools.BenchmarkGroup:
    tags: []
    "a" => BenchmarkTools.BenchmarkGroup:
	  tags: ["1", "2", "3"]
	  "d" => BenchmarkTools.BenchmarkGroup:
		  tags: ["8"]
		  1 => 1

julia> g[@tagged "9"]
BenchmarkTools.BenchmarkGroup:
  tags: []
  "a" => BenchmarkTools.BenchmarkGroup:
	  tags: ["1", "2", "3"]
	  "e" => BenchmarkTools.BenchmarkGroup:
		  tags: ["9"]
		  2 => 2

索引到 基准组,基准组 使用另一个 基准组,基准组

有时创建是有用的 基准组,基准组 钥匙是从一个 基准组,基准组,但值是从另一个绘制。 您可以通过索引到后者来完成此操作 Benchmark集团 与前者:

julia> g # leaf values are integers
BenchmarkTools.BenchmarkGroup:
  tags: []
  "c" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => 1
	  "2" => 2
	  "3" => 3
  "b" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => 1
	  "2" => 2
	  "3" => 3
  "a" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => 1
	  "2" => 2
	  "3" => 3
  "d" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => 1
	  "2" => 2
	  "3" => 3

julia> x # note that leaf values are characters
BenchmarkTools.BenchmarkGroup:
  tags: []
  "c" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "2" => '2'
  "a" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => '1'
	  "3" => '3'
  "d" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => '1'
	  "2" => '2'
	  "3" => '3'

julia> g[x] # index into `g` with the keys of `x`
BenchmarkTools.BenchmarkGroup:
  tags: []
  "c" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "2" => 2
  "a" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => 1
	  "3" => 3
  "d" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "1" => 1
	  "2" => 2
	  "3" => 3

这将是一个有用的示例场景:您有一组基准测试,以及一组相应的 TrialJudgement碌录潞陆s,并且您希望重新运行您的套件中被认为是判断组中的回归的基准。 您可以使用以下代码轻松执行此操作:

run(suite[regressions(judgements)])

索引到 基准组,基准组 使用 向量资料

您可能已经注意到嵌套 基准组,基准组 实例形成树状结构,其中根节点是父组,中间节点是子组,叶子取像试验数据和基准定义的值。

由于这些树可以是任意不对称的,因此编写某些树可能很麻烦 Benchmark集团 仅使用前面讨论过的索引工具进行转换。

为了解决这个问题,BenchmarkTools允许您使用一个唯一的索引组节点 向量资料 节点父节点的密钥。 例如:

julia> g = BenchmarkGroup([], 1 => BenchmarkGroup([], "a" => BenchmarkGroup([], :b => 1234)));

julia> g
BenchmarkTools.BenchmarkGroup:
  tags: []
  1 => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "a" => BenchmarkTools.BenchmarkGroup:
		  tags: []
		  :b => 1234

julia> g[[1]] # == g[1]
BenchmarkTools.BenchmarkGroup:
  tags: []
  "a" => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  :b => 1234
julia> g[[1, "a"]] # == g[1]["a"]
BenchmarkTools.BenchmarkGroup:
  tags: []
  :b => 1234
julia> g[[1, "a", :b]] # == g[1]["a"][:b]
1234

请记住,此索引方案也适用于 setindex!:

julia> g[[1, "a", :b]] = "hello"
"hello"

julia> g
BenchmarkTools.BenchmarkGroup:
  tags: []
  1 => BenchmarkTools.BenchmarkGroup:
	  tags: []
	  "a" => BenchmarkTools.BenchmarkGroup:
		  tags: []
		  :b => "hello"

分配到 Benchmark集团 用一个 向量资料 根据需要创建子组:

julia>  g[[2, "a", :b]] = "hello again"
"hello again"

julia> g
2-element BenchmarkTools.BenchmarkGroup:
  tags: []
  2 => 1-element BenchmarkTools.BenchmarkGroup:
          tags: []
          "a" => 1-element BenchmarkTools.BenchmarkGroup:
                  tags: []
                  :b => "hello again"
  1 => 1-element BenchmarkTools.BenchmarkGroup:
          tags: []
          "a" => 1-element BenchmarkTools.BenchmarkGroup:
                  tags: []
                  :b => "hello"

您可以使用 树叶 构造组的叶索引/值对的迭代器的函数:

julia> g = BenchmarkGroup(["1"],
                          "2" => BenchmarkGroup(["3"], 1 => 1),
                          4 => BenchmarkGroup(["3"], 5 => 6),
                          7 => 8,
                          9 => BenchmarkGroup(["2"],
                                              10 => BenchmarkGroup(["3"]),
                                              11 => BenchmarkGroup()));

julia> collect(leaves(g))
3-element Array{Any,1}:
 ([7],8)
 ([4,5],6)
 (["2",1],1)

请注意,终端子组节点不被 树叶 函数。

缓存 参数

BenchmarkTools中使用的常见工作流程如下:

  1. 开始Julia会话

  2. 使用旧版本的软件包执行基准测试套件 julia old_results=run(suite,verbose=true)

  3. 以某种方式保存结果(例如在JSON文件中) 朱莉娅BenchmarkTools.save("old_results.json",old_results)

  4. 开始新的Julia会话

  5. 使用新版本的软件包执行基准套件

  results = run(suite, verbose = true)
  1. 将新结果与步骤3中保存的结果进行比较,以确定回归状态 朱莉娅old_results=BenchmarkTools.load("old_results.json")BenchmarkTools。判断(最小(结果),最小(old_results))

此工作流存在几个问题,所有这些问题都围绕参数调整(在步骤2和5期间会发生):

*一致性:给予足够的时间,连续调用 调子! 通常会为"每个样本的评估"参数产生合理一致的值,即使有噪声。 但是,一些基准对此参数的轻微变化高度敏感。 因此,最好有一些保证,所有实验都是平等配置的(即,保证步骤2将使用与步骤5完全相同的参数)。 *周转时间:对于大多数基准, 调子! 需要执行许多评估以确定任何给定基准的适当参数-通常比运行试验时执行的评估更多。 事实上,基准测试的大部分时间通常用于调整参数,而不是实际运行试验。

BenchmarkTools通过允许您预先调整基准套件,保存"每个样本的评估"参数并按需加载来解决这些问题:

# untuned example suite
julia> suite
BenchmarkTools.BenchmarkGroup:
  tags: []
  "utf8" => BenchmarkGroup(["string", "unicode"])
  "trig" => BenchmarkGroup(["math", "triangles"])

# tune the suite to configure benchmark parameters
julia> tune!(suite);

# save the suite's parameters using a thin wrapper
# over JSON (this wrapper maintains compatibility
# across BenchmarkTools versions)
julia> BenchmarkTools.save("params.json", params(suite));

现在,而不是调整 套房 每次我们在一个新的Julia会话中加载基准时,我们都可以简单地使用 装货! 函数。 该 [1]负荷/负荷 call获取序列化到JSON文件中的第一个值,在本例中是参数。

# syntax is loadparams!(group, paramsgroup, fields...)
julia> loadparams!(suite, BenchmarkTools.load("params.json")[1], :evals, :samples);

以这种方式缓存参数会导致更短的周转时间,更重要的是,结果更加一致。

可视化基准测试结果

要比较两个或多个基准,您可以使用 IOContext 要设置 :组织蛋白:histmax:

julia> io = IOContext(stdout, :histmin=>0.5, :histmax=>8, :logbins=>true)
IOContext(Base.TTY(RawFD(13) open, 0 bytes waiting))

julia> b = @benchmark x^3   setup=(x = rand()); show(io, MIME("text/plain"), b)
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  1.239 ns … 31.433 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     1.244 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   1.266 ns ±  0.611 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

       █
  ▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  0.5 ns       Histogram: log(frequency) by time        8 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.
julia> b = @benchmark x^3.0 setup=(x = rand()); show(io, MIME("text/plain"), b)
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  5.636 ns … 38.756 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     5.662 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   5.767 ns ±  1.384 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

                                         █▆    ▂             ▁
  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███▄▄▃█▁▁▁▁▁▁▁▁▁▁▁▁ █
  0.5 ns       Histogram: log(frequency) by time        8 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

套装 :木箱真的错误 以确保所有使用相同的垂直缩放(对数频率或频率)。

审判 对象可以使用可视化 基准图 包裹:

using BenchmarkPlots, StatsPlots
b = @benchmarkable lu(rand(10,10))
t = run(b)

plot(t)

这将以小提琴情节的形式显示审判的计时结果。 您可以使用以下所有关键字参数 情节。jl,例如 st=:盒子亚克西斯=:log10.

如果一个 Benchmark集团 包含(只) 审判s,其结果可以通过简单的可视化

using BenchmarkPlots, StatsPlots
t = run(g)
plot(t)

这将显示每个 审判 作为小提琴情节。

杂项提示和信息

*BenchmarkTools将最小可测量基准执行时间限制为一皮秒。 *如果您使用 兰德 或者类似于生成基准测试中使用的值的东西,您应该对RNG进行种子处理(或提供种子处理的RNG),以便在试验/样本/评估之间的值一致。 *BenchmarkTools试图对_samples_之间发生的机器噪音保持稳健,但BenchmarkTools对_trials_之间发生的机器噪音做不了太多。 为了减少后一种噪声,建议您使用屏蔽工具将Cpu和内存专用于基准测试Julia进程,例如http://manpages.ubuntu.com/manpages/precise/man1/cset.1.html[cset]。 *在某些机器上,对于某些版本的BLAS和Julia,BLAS工作线程的数量可能超过可用内核的数量。 这有时会导致调度问题和BLAS-heavy基准的性能不一致。 要解决此问题,您可以使用 布拉斯。set_num_threads(i::Int) 在Julia REPL中确保BLAS线程的数量等于或小于可用内核的数量。 * @基准 在全局范围内进行评估,即使从本地范围调用也是如此。