Permit me to expand a bit on @kristoffer.carlsson’s point (since I for one didn’t get it at first), and see how code_warntype
might be used in cases like this.
I’ll add return values to verify type inference:
module M
bll(x,V,β)=exp(-β*V(x))
function runN(x,V,β,N)
a=one(eltype(x))
for n=1:N
a = exp(-β*V(x))
end
a
end
function runN2(x,V,β,N)
a=one(eltype(x))
for n=1:N
a = bll(x,V,β)
end
a
end
V(x) = (dot(x,x)-1)^2
function trialx(N=10^6)
x0=rand(2)
β=1.0
a=runN(x0,V,β,N)
b=runN2(x0,V,β,N)
a,b
end
end # module M
Simply running code_warntype
on runN
and runN2
with arguments as specified suggests they would be indistinguishable, as noted above. But let’s look where they are used:
julia> @code_warntype M.trialx(10^6)
Variables:
#self# <optimized out>
N::Int64
x0::Array{Float64,1}
β <optimized out>
a::Float64
b::Float64
Body:
begin
$(Expr(:inbounds, false))
# meta: location random.jl rand 285
# meta: location random.jl rand 284
# meta: location random.jl rand 387
# meta: location random.jl rand 390
SSAValue(1) = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Float64,1}, svec(Any, Int64), Array{Float64,1}, 0, 2, 0))
# meta: pop location
# meta: pop location
# meta: pop location
# meta: pop location
$(Expr(:inbounds, :pop))
x0::Array{Float64,1} = $(Expr(:invoke, MethodInstance for rand!(::MersenneTwister, ::Array{Float64,1}, ::Int64, ::Type{Base.Random.CloseOpen}), :(Base.Random.rand!), :(Base.Random.GLOBAL_RNG), SSAValue(1), :((Base.arraylen)(SSAValue(1))::Int64), :(Base.Random.CloseOpen))) # line 29:
a::Float64 = $(Expr(:invoke, MethodInstance for runN(::Array{Float64,1}, ::M.#V, ::Float64, ::Int64), :(M.runN), :(x0), :(M.V), 1.0, :(N))) # line 30:
b::Float64 = $(Expr(:invoke, MethodInstance for runN2(::Array{Float64,1}, ::Function, ::Float64, ::Int64), :(M.runN2), :(x0), :(M.V), 1.0, :(N))) # line 31:
return (Core.tuple)(a::Float64, b::Float64)::Tuple{Float64,Float64}
end::Tuple{Float64,Float64}
Overall inference looks good, but we see the generic method, which is typed as follows:
julia> code_warntype(M.runN2,(Vector{Float64},Function,Float64,Int))
Variables:
#self# <optimized out>
x::Array{Float64,1}
V::F
β::Float64
N::Int64
n <optimized out>
#temp#::Int64
a::Any
Body:
begin
a::Any = (Base.sitofp)(Float64, 1)::Float64 # line 38:
SSAValue(2) = (Base.select_value)((Base.sle_int)(1, N::Int64)::Bool, N::Int64, (Base.sub_int)(1, 1)::Int64)::Int64
#temp#::Int64 = 1
5:
unless (Base.not_int)((#temp#::Int64 === (Base.add_int)(SSAValue(2), 1)::Int64)::Bool)::Bool goto 14
SSAValue(3) = #temp#::Int64
SSAValue(4) = (Base.add_int)(#temp#::Int64, 1)::Int64
#temp#::Int64 = SSAValue(4) # line 39:
a::Any = (M.exp)(((Base.neg_float)(β::Float64)::Float64 * (V::F)(x::Array{Float64,1})::Any)::Any)::Any
12:
goto 5
14: # line 41:
return a::Any
end::Any
This seems to be the source of the inefficient allocations.
As Kristoffer mentions, adding the where
clause forces a well-typed specification instead.
The above was from Julia v0.6.2. On v0.7-nightly, runN2
is inlined by default, which avoids the issue. (Decorating with @noinline
reverts to a generic method, but a more efficient one than the v0.6 version.)