请在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
写n = 1000
会产生3个分配,但是n = 10000
会产生6个分配。 (不过,该模式并未严格继续:例如,(x + x + x).^2
仅产生n = 10000
5个分配。)
为什么向量的大小会影响发生多少分配?这是怎么回事?
这在JupyterLab控制台和普通的Julia REPL中都发生。
为什么有一个小向量分配,而两个大向量分配?
真的,这无关紧要,它是数组如何工作的内部细节。本质上,Julia Array
有两个部分:内部标头(跟踪数组的维数和元素类型等)以及数据本身。当阵列较小时,将这两个数据段捆绑在一起是有优势的,但是当阵列较大时,将它们分开是有利的。这不是广播,而是数组分配:
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_llvm
,@code_lowered
,@code_native
,@code_typed
和@code_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
)