为什么将 for 循环移动到列表理解会使函数类型不稳定?
Why moving a foor loop to list comprehension makes a function type-unstable?
看看这些功能:
function fA1(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(T)
for i in 1:n
x = x+x
end
return x
end
function fA2(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(Float64)
for i in 1:n
x = x+x
end
return x
end
function fB1(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(T)
[x = x+x for i in 1:n]
return x
end
function fB2(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(Float64)
[x = x+x for i in 1:n]
return x
end
fA1
类型不稳定且速度慢,fA2
类型稳定且速度快。
但是,当我将 for
循环作为列表理解移动时,fB1
和 fB2
都是类型不稳定且缓慢的,而数值结果(显然)保持不变。
这是为什么?
Julia 手册中解释了原因 here。
重要的是要知道理解会创建一个新的本地范围,如 Julia 手册中所述here。
在这种情况下,如果你坚持从外部作用域更新变量(通常不推荐这样做,因为阅读此类代码的人通常会感到困惑),你能做的最好的就是我知道(也许有人可以想出更好的解决方案,但考虑到 Julia 编译器的当前状态,我认为这不太可能)是使用类型注释:
function fB2(n)
x::Float64 = one(Float64)
[x = x+x for i in 1:n]
return x
end
这不会避免装箱,但应该使 return 类型可推断并且性能应该显着提高。
将来 Julia 编译器很可能会足够智能,无需类型注释即可处理此类代码。
这是性能比较:
julia> using BenchmarkTools
julia> function f_fast(n)
x::Float64 = one(Float64)
[x = x+x for i in 1:n]
return x
end
f_fast (generic function with 1 method)
julia> function f_slow(n)
x = one(Float64)
[x = x+x for i in 1:n]
return x
end
f_slow (generic function with 1 method)
julia> @benchmark f_fast(1000)
BenchmarkTools.Trial:
memory estimate: 23.63 KiB
allocs estimate: 1004
--------------
minimum time: 4.357 μs (0.00% GC)
median time: 7.257 μs (0.00% GC)
mean time: 10.314 μs (16.54% GC)
maximum time: 5.256 ms (99.86% GC)
--------------
samples: 10000
evals/sample: 7
julia> @benchmark f_slow(1000)
BenchmarkTools.Trial:
memory estimate: 23.66 KiB
allocs estimate: 1005
--------------
minimum time: 17.899 μs (0.00% GC)
median time: 26.300 μs (0.00% GC)
mean time: 34.916 μs (15.56% GC)
maximum time: 36.220 ms (99.91% GC)
--------------
samples: 10000
evals/sample: 1
看看这些功能:
function fA1(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(T)
for i in 1:n
x = x+x
end
return x
end
function fA2(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(Float64)
for i in 1:n
x = x+x
end
return x
end
function fB1(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(T)
[x = x+x for i in 1:n]
return x
end
function fB2(s::AbstractString, n)
T = getfield(Base, Symbol(s))
x = one(Float64)
[x = x+x for i in 1:n]
return x
end
fA1
类型不稳定且速度慢,fA2
类型稳定且速度快。
但是,当我将 for
循环作为列表理解移动时,fB1
和 fB2
都是类型不稳定且缓慢的,而数值结果(显然)保持不变。
这是为什么?
Julia 手册中解释了原因 here。
重要的是要知道理解会创建一个新的本地范围,如 Julia 手册中所述here。
在这种情况下,如果你坚持从外部作用域更新变量(通常不推荐这样做,因为阅读此类代码的人通常会感到困惑),你能做的最好的就是我知道(也许有人可以想出更好的解决方案,但考虑到 Julia 编译器的当前状态,我认为这不太可能)是使用类型注释:
function fB2(n)
x::Float64 = one(Float64)
[x = x+x for i in 1:n]
return x
end
这不会避免装箱,但应该使 return 类型可推断并且性能应该显着提高。
将来 Julia 编译器很可能会足够智能,无需类型注释即可处理此类代码。
这是性能比较:
julia> using BenchmarkTools
julia> function f_fast(n)
x::Float64 = one(Float64)
[x = x+x for i in 1:n]
return x
end
f_fast (generic function with 1 method)
julia> function f_slow(n)
x = one(Float64)
[x = x+x for i in 1:n]
return x
end
f_slow (generic function with 1 method)
julia> @benchmark f_fast(1000)
BenchmarkTools.Trial:
memory estimate: 23.63 KiB
allocs estimate: 1004
--------------
minimum time: 4.357 μs (0.00% GC)
median time: 7.257 μs (0.00% GC)
mean time: 10.314 μs (16.54% GC)
maximum time: 5.256 ms (99.86% GC)
--------------
samples: 10000
evals/sample: 7
julia> @benchmark f_slow(1000)
BenchmarkTools.Trial:
memory estimate: 23.66 KiB
allocs estimate: 1005
--------------
minimum time: 17.899 μs (0.00% GC)
median time: 26.300 μs (0.00% GC)
mean time: 34.916 μs (15.56% GC)
maximum time: 36.220 ms (99.91% GC)
--------------
samples: 10000
evals/sample: 1