Julia @code_warntype reveals hidden temporary variable #temp#

554 Views Asked by At

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}...)

1

There are 1 best solutions below

1
On BEST ANSWER

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.