将@time为Julia中的大向量报告的分配加倍
考虑Julia中的以下简单程序:将@time为Julia中的大向量报告的分配加倍,time,julia,allocation,Time,Julia,Allocation,考虑Julia中的以下简单程序: function foo_time(x) @time x.^2 return nothing end n = 1000; foo_time(collect(1:n)); 如果我在控制台中运行它,那么@time报告1个分配,这就是我所期望的。但是,如果我将n更改为10000,则@time报告2次分配 更重要的是,如果我在没有语法循环融合(换句话说,没有点)的情况下将函数链接在一起,那么我似乎得到了预期分配的两倍。例如,编写(x+x)。^2+x而不
function foo_time(x)
@time x.^2
return nothing
end
n = 1000;
foo_time(collect(1:n));
如果我在控制台中运行它,那么@time
报告1个分配,这就是我所期望的。但是,如果我将n
更改为10000
,则@time
报告2次分配
更重要的是,如果我在没有语法循环融合(换句话说,没有点)的情况下将函数链接在一起,那么我似乎得到了预期分配的两倍。例如,编写(x+x)。^2+x
而不是x.^2
会产生3个n=1000
的分配,但它会产生6个n=10000
的分配。(但该模式并没有严格地继续:例如,(x+x+x)。^2
只为n=10000
生成5个分配)
为什么向量的大小会影响分配的数量?引擎盖下面是怎么回事
这在JupyterLab控制台和正常的Julia REPL中都会发生。为什么有一个小向量分配和两个大向量分配 实际上,这并不重要,只是阵列工作原理的内部细节。基本上,Julia
数组有两个部分:内部标头(用于跟踪数组的维度和元素类型等),以及数据本身。当阵列较小时,将这两个数据段捆绑在一起是一个优势,但当阵列较大时,将它们分开是一个优势。这不是广播问题,只是阵列分配问题:
julia> f(n) = (@time Vector{Int}(undef, n); nothing)
f (generic function with 1 method)
julia> f(2048)
0.000003 seconds (1 allocation: 16.125 KiB)
julia> f(2049)
0.000003 seconds (2 allocations: 16.141 KiB)
然后,希望您能够理解为什么在涉及临时变量的情况下,这会导致大型数组的分配数量增加一倍—每个数组的头有一个,每个数组的数据有一个
简言之,不要太担心分配的数量。有时,分配实际上可以提高性能。然而,当你看到大量的分配时,尤其是当你看到它们与数组中元素的数量成正比时。我同意Matt的观点,对于这个简单的任务,分配的数量不是一个好的指标
如果您想深入了解细节并准确理解代码是如何编译和执行的,我建议您使用这些宏@code\u llvm
,@code\u lowered
,@code\u native
,@code\u typed
和@code\u warntype
。Julia doc和中详细解释了这些宏之间的所有细微差别
julia> f(x) = x.^2
f (generic function with 1 method)
julia> @code_lowered f(randn(10000))
CodeInfo(
1 ─ %1 = (Core.apply_type)(Base.Val, 2)
│ %2 = (%1)()
│ %3 = (Base.broadcasted)(Base.literal_pow, Main.:^, x, %2)
│ %4 = (Base.materialize)(%3)
└── return %4
)
julia> f2(x) = (x + x).^2 + x
f2 (generic function with 1 method)
julia> @code_lowered f2(randn(10000))
CodeInfo(
1 ─ %1 = x + x
│ %2 = (Core.apply_type)(Base.Val, 2)
│ %3 = (%2)()
│ %4 = (Base.broadcasted)(Base.literal_pow, Main.:^, %1, %3)
│ %5 = (Base.materialize)(%4)
│ %6 = %5 + x
└── return %6
)
对于小型阵列不存在的大型阵列,将标头和数据本身分开有什么好处?相反,对于小型阵列,将标头和数据保持在一起是一种优化。如果阵列很小,那么单独的头分配的成本与数据分配相比是显著的,但是如果它们很大,那么成本相对来说是微不足道的。还有一个额外的考虑,如果阵列增长,那么能够独立地对旧的(现在太小的)数据区域进行垃圾收集是很好的,特别是如果它很大的话!