将@time为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而不

考虑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
而不是
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
)

对于小型阵列不存在的大型阵列,将标头和数据本身分开有什么好处?相反,对于小型阵列,将标头和数据保持在一起是一种优化。如果阵列很小,那么单独的头分配的成本与数据分配相比是显著的,但是如果它们很大,那么成本相对来说是微不足道的。还有一个额外的考虑,如果阵列增长,那么能够独立地对旧的(现在太小的)数据区域进行垃圾收集是很好的,特别是如果它很大的话!