I ran a type-stability check on my code recently. When I call @code_warntype
on it, I get the following output:
Variables:
#unused#::IHT.#kw##L0_reg
#temp#@_2::Array{Any,1}
::IHT.#L0_reg
x::Array{Float64,2}
y::Array{Float64,1}
k::Int64
#temp#@_7::Bool
#temp#@_8::Bool
max_iter::Int64
max_step::Int64
quiet::Bool
v::IHT.IHTVariables{Float64,Array{Float64,1}}
tol::Float64
#temp#@_14::Int64
#temp#@_15::Int64
#temp#@_16::Int64
#temp#@_17::Any
###...###
#temp#@_17::Any = (Core.arrayref(#temp#@_2::Array{Any,1},#temp#@_16::Int64)::Any
###...###
v::IHT.IHTVariables{Float64,Array{Float64,1}} = (Core.typeassert)((Core.arrayref)(#temp#@_2::Array{Any,1},(Base.box)(Int64,(Base.add_int)(#temp#@_16::Int64,1)))::Any,IHT.IHTVariables{Float64,Array{Float64,1}})::IHT.IHTVariables{Float64,Array{Float64,1}}
Minimal working example, using my IHT.jl package:
Pkg.clone("https://github.com/klkeys/IHT.jl")
n = 100; p = 250; k = 2;
x = randn(n,p)
b = zeros(p); b[1:k] = randn(k); shuffle!(b)
y = x*b + randn(n)
@code_warntype L0_reg(x, y, k, quiet=true)
It would seem like the compiler is using #temp#
to read the arguments to the function L0_reg
. The function arguments are completely specified. From where does this evil little #temp#@_2
variable arise? Am I able to tell the compiler what its type is? (hopefully not Array{Any,1}
...)
You can use @code_lowered
to view where the #temp#
variables are coming from:
julia> @code_lowered L0_reg(x, y, k, quiet=true)
LambdaInfo template for (::IHT.#kw##L0_reg){T<:Union{Float32,Float64}}(::Array{Any,1}, ::IHT.#L0_reg, x::DenseArray{T,2}, y::DenseArray{T,1}, k::Int64)
:(begin
NewvarNode(:(temp))
NewvarNode(:(tol))
#temp#@_7 = true
#temp#@_8 = true
max_iter = 100
max_step = 50
quiet = true
SSAValue(0) = (IHT.colon)(1,(Base.length)(#temp#@_2) >> 1)
#temp#@_14 = (Base.start)(SSAValue(0))
10:
unless !((Base.done)(SSAValue(0),#temp#@_14)) goto 42
SSAValue(1) = (Base.next)(SSAValue(0),#temp#@_14)
#temp#@_15 = (Core.getfield)(SSAValue(1),1)
#temp#@_14 = (Core.getfield)(SSAValue(1),2)
#temp#@_16 = #temp#@_15 * 2 - 1
#temp#@_17 = (Core.arrayref)(#temp#@_2,#temp#@_16)
unless #temp#@_17 === :quiet goto 20
quiet = (Core.typeassert)((Core.arrayref)(#temp#@_2,#temp#@_16 + 1),IHT.Bool)
goto 40
20:
unless #temp#@_17 === :max_step goto 24
max_step = (Core.typeassert)((Core.arrayref)(#temp#@_2,#temp#@_16 + 1),IHT.Int)
goto 40
24:
unless #temp#@_17 === :max_iter goto 28
max_iter = (Core.typeassert)((Core.arrayref)(#temp#@_2,#temp#@_16 + 1),IHT.Int)
goto 40
28:
unless #temp#@_17 === :tol goto 33
tol = (Core.typeassert)((Core.arrayref)(#temp#@_2,#temp#@_16 + 1),IHT.Float)
#temp#@_8 = false
goto 40
33:
unless #temp#@_17 === :temp goto 38
temp = (Core.typeassert)((Core.arrayref)(#temp#@_2,#temp#@_16 + 1),(Core.apply_type)(IHT.IHTVariables,$(Expr(:static_parameter, 1))))
#temp#@_7 = false
goto 40
38:
(Base.kwerr)(#temp#@_2,,x,y,k)
40:
goto 10
42:
unless #temp#@_7 goto 45
temp = (IHT.IHTVariables)(x,y,k)
45:
unless #temp#@_8 goto 48
tol = (IHT.convert)($(Expr(:static_parameter, 1)),0.0001)
48:
return (IHT.#L0_reg#75)(temp,tol,max_iter,max_step,quiet,,x,y,k)
end)
In this case, the temps (in particular #temp#@_2
) are coming from the keyword arguments. This is quite normal for keyword arguments.
julia> f(; x=1) = x
f (generic function with 1 method)
julia> @code_warntype f(x=1)
Variables:
#unused#::#kw##f
#temp#@_2::Array{Any,1}
::#f
x::Any
#temp#@_5::Int64
#temp#@_6::Int64
#temp#@_7::Int64
#temp#@_8::Any
Body:
begin
x::Any = 1
SSAValue(2) = (Base.arraylen)(#temp#@_2::Array{Any,1})::Int64
SSAValue(3) = (Base.select_value)((Base.sle_int)(0,1)::Bool,(Base.box)(Int64,(Base.ashr_int)(SSAValue(2),(Base.box)(UInt64,1))),(Base.box)(Int64,(Base.shl_int)(SSAValue(2),(Base.box)(UInt64,(Base.box)(Int64,(Base.neg_int)(1))))))::Int64
SSAValue(4) = (Base.select_value)((Base.sle_int)(1,SSAValue(3))::Bool,SSAValue(3),(Base.box)(Int64,(Base.sub_int)(1,1)))::Int64
#temp#@_5::Int64 = 1
6:
unless (Base.box)(Base.Bool,(Base.not_int)((#temp#@_5::Int64 === (Base.box)(Int64,(Base.add_int)(SSAValue(4),1)))::Bool)) goto 21
SSAValue(5) = #temp#@_5::Int64
SSAValue(6) = (Base.box)(Int64,(Base.add_int)(#temp#@_5::Int64,1))
#temp#@_6::Int64 = SSAValue(5)
#temp#@_5::Int64 = SSAValue(6)
#temp#@_7::Int64 = (Base.box)(Int64,(Base.sub_int)((Base.box)(Int64,(Base.mul_int)(#temp#@_6::Int64,2)),1))
#temp#@_8::Any = (Core.arrayref)(#temp#@_2::Array{Any,1},#temp#@_7::Int64)::Any
unless (#temp#@_8::Any === :x)::Bool goto 17
x::Any = (Core.arrayref)(#temp#@_2::Array{Any,1},(Base.box)(Int64,(Base.add_int)(#temp#@_7::Int64,1)))::Any
goto 19
17:
(Base.throw)($(Expr(:new, :(Base.MethodError), :((Core.getfield)((Core.getfield)((Core.getfield)(#f,:name)::TypeName,:mt),:kwsorter)), :((Core.tuple)(#temp#@_2,)::Tuple{Array{Any,1},#f}))))::Union{}
19:
goto 6
21:
return x::Any
end::Any
Keyword arguments are known to have callsite overhead that can be somewhat worked around by declaring types. Note that unless your function does very little, it's unlikely the sorting of keyword arguments is actually a huge bottleneck (despite the nasty @code_warntype
output).
When you do @code_warntype
on a keyword argument call, you're actually viewing the type instabilities of the keyword argument sorter, an autogenerated wrapper around the real function. As you can see, the code ends up calling (IHT.#L0_reg#75)(temp,tol,max_iter,max_step,quiet,,x,y,k)
, which is a plain function that takes positional arguments. So the output of @code_warntype
is almost useless in this case.