@DSM的回答是很好的。然而,除此之外,还有一些事情我想补充解释一下。你的for循环慢的原因是因为A
是一个非常量的全局变量,而且你的代码在直接改变这个全局变量。由于A
不是常量,代码必须防止在循环执行期间A
变成不同类型的不同值。代码必须在每次循环迭代中查找A
的类型和位置,并动态分派表达式A[ii, jj] = A[ii, jj] + 1.0
中的方法调用 - 这是一个对getindex
、+
和setindex!
的调用,所有这些都依赖于静态未知的A
类型。你可以通过将其封装在一个函数中来立即获得更好的性能:
julia> A = rand(2^10, 2^10);
julia> @time for jj = 1:size(A, 2), ii = 1:size(A, 1)
A[ii, jj] += 1
end
elapsed time: 0.288340785 seconds (84048040 bytes allocated, 15.59% gc time)
julia> function inc!(A)
for jj = 1:size(A, 2), ii = 1:size(A, 1)
A[ii, jj] += 1
end
end
inc! (generic function with 1 method)
julia> @time inc!(A)
elapsed time: 0.006076414 seconds (171336 bytes allocated)
julia> @time inc!(A)
elapsed time: 0.000888457 seconds (80 bytes allocated)
避免使用非常量全局变量是手册中第一个建议,详见
Performance Tips 部分。您可能还想浏览本章的其余内容。
我们可以进一步改善
inc!
函数的性能,使用
@inbounds
注释指示此代码不需要边界检查,并使用线性索引而非二维索引:
julia> function inc!(A)
@inbounds for i = 1:length(A)
A[i] += 1
end
end
inc! (generic function with 1 method)
julia> @time inc!(A)
elapsed time: 0.000637934 seconds (80 bytes allocated)
大部分加速来自于@inbounds
注释,而不是线性索引,尽管它确实可以稍微提高一点速度。然而,应该只在确定索引不会越界且性能至关重要的情况下,谨慎使用@inbounds
注释。如您所见,额外的性能提升虽然存在,但并不是压倒性的。大部分好处来自于不直接更改全局变量。
broadcast!
使用的大部分时间和内存都来自编译;第二次运行时,两者都会显著减少。 - tholy