Why am I getting type instability?

I’m trying to access linked entries in a sparse matrix where only the values change and thought it would be faster to save the locations of the values, but accessing them like this is much slower than just matrix indexing the sparse matrix and setting the values by hand. Inspecting with @code_warntype reveals a lot of red lines.

I made the type

mutable struct Observer{T,R}
    ref::R
    pair::Pair{Int32,Int32}
    nzidx1::Int32
    nzidx2::Int32
    val::T
end

Which I for example initialize as

sp # Some SparseMatrixCSC
ob = Observer(sp, 1=>2, 1, 101, sp.nzval[1])

@code_warntype ob.ref
MethodInstance for getproperty(::Observer{Float32, SparseMatrixCSC{Float32, Int32}}, ::Symbol)
  from getproperty(x, f::Symbol) @ Base Base.jl:37
Arguments
  #self#::Core.Const(getproperty)
  x::Observer{Float32, SparseMatrixCSC{Float32, Int32}}
  f::Symbol
Body::Any
1 ─      nothing
│   %2 = Base.getfield(x, f)::Any
└──      return %2

Get field seems to be type instable?
Then I’m trying to set them through a dict of these things, and @code_warntype reveals a lot of red.

function setval(ob::Observer, val)
    ob.ref.nzval[ob.nzidx1] = val
    ob.ref.nzval[ob.nzidx2] = val
end

struct ObserverList{A, B} <: AbstractDict{A, B}
    observers::Dict{A, B}
end

function setval(obs::ObserverList{A,Observer{T,R}}, val, pair) where {A, T, R<:SparseMatrixCSC{T}}
    if haskey(obs, pair)
        setval(obs[pair], val)
        return val
    end
    return val
end

@code_warntype setval(observers,1, 1=>2)
MethodInstance for setval(::ObserverList{Pair{Int32, Int32}, Observer{Float32}}, ::Int64, ::Pair{Int32, Int32})
  from setval(obs::ObserverList, val, pair) @ Main ~/Documents/GitHub/InteractiveRBM.jl/ObserverList.jl:41
Arguments
  #self#::Core.Const(setval)
  obs::ObserverList{Pair{Int32, Int32}, Observer{Float32}}
  val::Int64
  pair::Pair{Int32, Int32}
Locals
  observer::Observer{Float32}
Body::Union{Nothing, Int64}
1 ─       Core.NewvarNode(:(observer))
│   %2  = Main.haskey(obs, pair)::Bool
└──       goto #3 if not %2
2 ─       (observer = Base.getindex(obs, pair))
│   %5  = Base.getproperty(observer, :ref)::SparseMatrixCSC
│   %6  = Base.getproperty(%5, :nzval)::Vector
│   %7  = Base.getproperty(observer, :nzidx1)::Int32
│         Base.setindex!(%6, val, %7)
│   %9  = Base.getproperty(observer, :ref)::SparseMatrixCSC
│   %10 = Base.getproperty(%9, :nzval)::Vector
│   %11 = Base.getproperty(observer, :nzidx2)::Int32
│         Base.setindex!(%10, val, %11)
└──       return val
3 ─       return 

I’m open to this being a dumb idea, but I am genuinely a bit confused why the code ends up being type instable…

I can’t tell what’s up in the second case from my phone, but I believe the problem in the first code snippet is using a global variable.

Try adding const before ob, and I think that should fix it.

if you put this in a function it will be type stable. At top level, you’re calling getproperty(ob, :ref) and essentially :ref is just a value::Symbol, it is type unstable because you could have also put any Symbol in place of :ref.

julia> ob = Observer(sp, Int32(1)=>Int32(2), Int32(1), Int32(101), sp.nzval[1])
Observer{Float64, SparseMatrixCSC{Float64, Int64}}(sparse([1, 2, 3, 1, 2, 3, 1, 2, 3], [1, 1, 1, 2, 2, 2, 3, 3, 3], [0.509841105706249, 0.43349375105674504, 0.451087020785071, 0.48967587202828466, 0.4581567204538729, 0.0076022387129910385, 0.5606083626338405, 0.5221675969765492, 0.29058394151780376], 3, 3), 1 => 2, 1, 101, 0.509841105706249)

julia> f(x) = x.ref
f (generic function with 1 method)

julia> @code_warntype f(ob)
MethodInstance for f(::Observer{Float64, SparseMatrixCSC{Float64, Int64}})
  from f(x) @ Main REPL[12]:1
Arguments
  #self#::Core.Const(f)
  x::Observer{Float64, SparseMatrixCSC{Float64, Int64}}
Body::SparseMatrixCSC{Float64, Int64}
1 ─ %1 = Base.getproperty(x, :ref)::SparseMatrixCSC{Float64, Int64}
└──      return %1

I tried this but it does not. Putting it into another function like @jling suggested does work. Then still when accessing it through the Dict which is concretely types I’m still getting a lot of type instability. For example it can’t infer the type of the SparseMatrix even though everything should be concretely typed. I had thought that setting elements this way, e.g. doing a dict lookup and then having the exact indexes to update should’ve been quite a bit faster than doing a search within a column twice in a sparse matrix, but for my usecase (which admittedly is not a huge SparseMatrix) it seems ~3x as slow. I was thinking this might be due to the type instability, but I’m open to other suggestions.

Okay, I found out what the problem was. This method is indeed a lot faster at setting elements in a sparsearray (~10x). The problem was I wrote a function setting up the dict from a SparseArray, and in the process didn’t realise that my object observers was of type Observers{Pair{Int32,Int32}, Observer{Float32}} which is not a concrete type. I think I made the false assumption that subtypes of DataTypes have to be concrete, but this is clearly false sinse Observer{Float32} is not a concrete type. I fixed this and now it’s blazing fast!

1 Like