我正在阅读 Julia 性能提示, https://docs.julialang.org/en/v1/manual/performance-tips/
一开始就提到了两个例子。
例一,
julia> x = rand(1000);
julia> function sum_global()
s = 0.0
for i in x
s += i
end
return s
end;
julia> @time sum_global()
0.009639 seconds (7.36 k allocations: 300.310 KiB, 98.32% compilation time)
496.84883432553846
julia> @time sum_global()
0.000140 seconds (3.49 k allocations: 70.313 KiB)
496.84883432553846
Run Code Online (Sandbox Code Playgroud)
我们看到很多内存分配。
现在例子2,
julia> x = rand(1000);
julia> function sum_arg(x)
s = 0.0
for i in x
s += i
end
return s
end;
julia> @time sum_arg(x)
0.006202 seconds (4.18 k allocations: 217.860 KiB, 99.72% compilation time)
496.84883432553846
julia> @time sum_arg(x)
0.000005 seconds (1 allocation: 16 bytes)
496.84883432553846
Run Code Online (Sandbox Code Playgroud)
我们看到,通过将 x 放入函数的参数中,内存分配几乎消失了,速度也快了很多。
我的问题是,谁能解释一下,
为什么示例 1 需要这么多分配,为什么示例 2 不需要与示例 1 一样多的分配?我有点困惑。
在这两个例子中,我们看到第二次运行 Julia 时,它总是比第一次快。这是否意味着我们需要运行 Julia 两次?如果 Julia 只在第二次运行时很快,那么重点是什么?为什么 Julia 不先编译,然后运行,就像 Fortran 一样?
是否有阻止内存分配的一般规则?还是我们总是需要做一个@time 来确定问题?
谢谢!
为什么示例 1 需要这么多分配,为什么示例 2 不需要与示例 1 一样多的分配?
示例 1 需要如此多的分配,因为x是一个全局变量(在函数范围之外定义sum_arg)。因此,变量的类型x可能随时改变,即有可能:
x和sum_argsum_argx(更改其类型)并运行sum_arg特别是,由于 Julia 支持多线程,一般而言,步骤 3 中的两个操作甚至可以并行发生(即,您可以x在一个线程中更改类型,同时sum_arg在另一个线程中运行)。
所以因为编译后sum_arg的类型x可以改变 Julia,所以在编译时sum_arg必须确保编译后的代码不依赖于x编译时存在的类型。相反,在这种情况下,Julia 允许x动态更改的类型。然而, allowed 的这种动态特性x意味着它必须在运行时(而不是编译时)进行检查。这种动态检查x会导致性能下降和内存分配。
您可以通过声明x为 a 来解决这个问题const(以const确保类型x不会改变):
julia> const x = rand(1000);
julia> function sum_global()
s = 0.0
for i in x
s += i
end
return s
end;
julia> @time sum_global() # this is now fast
0.000002 seconds
498.9290555615045
Run Code Online (Sandbox Code Playgroud)
为什么 Julia 不先编译,然后运行,就像 Fortran 一样?
这正是朱莉娅所做的。但是,Julia 的好处是它会在需要时自动进行编译。这使您可以进行流畅的交互式开发过程。
如果您愿意,可以在使用该precompile函数运行之前编译该函数,然后单独运行它。然而,通常人们只是运行该函数而没有明确地执行它。
结果是,如果您使用@time:
是否有阻止内存分配的一般规则?
这些规则在您在问题中引用的手册的性能提示部分中完全给出。使用提示@time是那里的诊断提示。所有其他提示都是为获得快速代码而推荐的规则。但是,我知道该列表很长,因此根据我的经验,可以从一个较短的列表开始: