Fixing red bars at top of flame profile

I’ve attached a screenshot of my flame profile. I’ve read that red bars near the top signal a problem with type instability. But I have specified the type of every struct member and the vast majority of my code relies on side effects, not return values.

Here’s one function that is causing multiple red bars, It gets called a lot, so it makes sense that it shows a wide bar in the flame profile. But I don’t know what’s causing the redness. Anyone have any insights?

function augmentCount!(base, cd, fvi, fv, fromTmp; w = 1, inc=1, numIndicesCutoff = 10_000) #w is width, inc is count to increment
    cdtw = cd.taskDat
    c = 0
    if fromTmp
        while length(cdtw.countsLow[w]) < fvi
            push!(cdtw.countsLow[w], 0)
        end
        if cdtw.countsLow[w][fvi] + inc < typemax(UInt8)
            c = cdtw.countsLow[w][fvi] += inc
        elseif cdtw.countsLow[w][fvi] < typemax(UInt8)
            cdtw.countsLow[w][fvi] = typemax(UInt8)
            base.countsMed[w][fv] = inc + cdtw.countsLow[w][fvi]
            c = base.countsMed[w][fv]
        elseif haskey(base.countsMed[w], fv) 
            if base.countsMed[w][fv] + inc <= typemax(UInt16)
                c = base.countsMed[w][fv] += inc
            else 
                c = base.countsHi[w][fv] = inc + base.countsMed[w][fv]
                delete!(base.countsMed[w], fv)
            end
        else c = base.countsHi[w][fv] += inc
        end
    else
        if base.countsLow[w][fvi] + inc < typemax(UInt8)
            c = base.countsLow[w][fvi] += inc
        elseif base.countsLow[w][fvi] < typemax(UInt8)
            base.countsLow[w][fvi] = typemax(UInt8)
            c = base.countsMed[w][fvi] = d.countsLow[fvi] + inc
        elseif haskey(base.countsMed[w], fvi) 
            if base.countsMed[w][fvi] + inc <= typemax(UInt16)
                c = base.countsMed[w][fvi] += inc
            else delete!(base.countsMed[w], fvi)
                base.countsHi[w][fvi] += inc
            end
        else base.countsHi[w][fvi] += inc
        end
    end
    return c
end

One sample call: c = augmentCount!(base, cd, fvi, fv, true; w=1)
Another: c2 = augmentCount!(base, cd, binFvi, binFv, fromTmp; w=2)

The types of the args are as follows: base is an immutable struct, cd is a mutable struct, fvi and binfvi are Ints, fv and binFV are small tuples, fromTmp is a Bool, and w is an Int. Would it help if I posted the contents of the structs?

Would help to show all the struct definitions. It’s easy enough to make a mistake in typing such that a parametric type is underspecified or so.

And ideally, we would be able to run your code for testing, but that might not be feasible.

I tried restructuring my code to move some variables out of a mutable struct and into an immutable structure, but it didn’t change much. The modified first three lines of the function are

function augmentCount!(base, td, fvi, fv, fromTmp; w = 1, inc=1, numIndicesCutoff = 10_000) #w is width, inc is count to increment
    c = 0
    if fromTmp
...

The td var is typed as

struct TaskDat_Pass1 <: AbstractTaskDat

    attIdToFeatValList::Tuple{
        Vector{Tuple{UInt32, UInt16, UInt16}},
        Vector{Tuple{UInt32, UInt32}}   } 

    featValToAttIdDict::Tuple{
        Dict{Tuple{UInt32, UInt16, UInt16}, UInt32},
        Dict{Tuple{UInt32, UInt32}, UInt64}     }    
        
    countsLow::Tuple{Vector{UInt8}, Vector{UInt8}}
end

and the base var is

struct GlobalBase
    featSpecList::Vector{FeatSpec}
    featSpecDict::Dict{String, UInt16}

    relationSet::Set{UInt8}     # relations
    coreFeatSet::Set{UInt8}     # upos, entity, caps, tense, number, headoffset, etc.
    basicFeatSet::Set{UInt8}    # relations plus core feats
    miscFeatSet::Set{UInt8}     # same-sen lemmas, same-doc lemmas
    formSet::Set{String}        # set of form strings from Wiktionary

    featValListSorted::Tuple{
        Vector{Tuple{UInt32, UInt8, UInt8}}, #, UInt8, UInt8}}
        Vector{NTuple{2,UInt32}},  
        Vector{NTuple{3,UInt32}}, 
        Vector{NTuple{4,UInt32}},  
        Vector{NTuple{5,UInt32}}  }

    countsLow::Tuple{
        Vector{UInt8}, 
        Vector{UInt8}, 
        Vector{UInt8}, 
        Vector{UInt8}, 
        Vector{UInt8}} 
    countsMed::Tuple{
        Dict{Tuple{UInt32, UInt16, UInt16}, UInt16},
        Dict{NTuple{2, UInt32}, UInt16},
        Dict{NTuple{3, UInt32}, UInt16},
        Dict{NTuple{4, UInt32}, UInt16},
        Dict{NTuple{5, UInt32}, UInt16} }
    countsHi::Tuple{
        Dict{Tuple{UInt32, UInt16, UInt16}, UInt32},
        Dict{NTuple{2, UInt32}, UInt32},
        Dict{NTuple{3, UInt32}, UInt32},
        Dict{NTuple{4, UInt32}, UInt32},
        Dict{NTuple{5, UInt32}, UInt32} }
    
    #indices::Vector{Vector{Vector{UInt8}}}    # N-ary x FvId x Indices
    odds::Tuple{
        Vector{Float16},
        Vector{Float16},
        Vector{Float16},
        Vector{Float16},
        Vector{Float16} } 
end

The other parameters are scalars. It would be difficult to post some standalone runnable code, but perhaps I could set up a repo on Github or something? Would that help?

I’m not entirely sure if this is it, but one potential type-stability problem is that countsMed and a couple of other members are heterogeneous tuples. These are then accessed as countsMed[w], but since w is not known at compile time there is no way to know what type you are accessing, leading to type instability.

Although I’ve never actually used it myself, GitHub - JuliaDebug/Cthulhu.jl: The slow descent into madness seems like it might be a good tool for nailing down where the inference fails.

I think tuples are not the issue, because

julia> isconcretetype(Tuple{
        Dict{Tuple{UInt32, UInt16, UInt16}, UInt16},
        Dict{NTuple{2, UInt32}, UInt16},
        Dict{NTuple{3, UInt32}, UInt16},
        Dict{NTuple{4, UInt32}, UInt16},
        Dict{NTuple{5, UInt32}, UInt16} })
true

But I second the suggestion to give Cthulhu a try and use it to step through the offending call.
With the right toggles set it basically shows you the @code_warntype output on every level of the call stack.

That could be it. If you can’t get w to constprop, you could think about pulling that integer into the type domain by making it a Val(1) etc. argument. Then you’d be sure that it can be type stable for each w.

Thank you, thank you for the suggestions. I will try them out!

Sometimes when I try to descend when using Cthulhu, I get the following error: ERROR: AssertionError: kind(sig) == K"call". What does it mean and how do I deal with it?

Try updating Cthulhu, the latest version is 2.9.2. If the doesn’t fix it, if you could share the code necessary to reproduce the error that would help in fixing it.

I have narrowed down my problem with Cthulhu to the following code snippet. The f.coreF function is held as a field of a struct and it returns a limited range of diverse values. I can’t avoid not knowing ahead of time what the return type will be (although it gets standardized to a string right away). My question is why does Cthulhu return an error: “ERROR: type Union has no field parameters” or a similar error message about the type of the TopOfBottom? Is there a better way to do what I’m doing?

struct FeatSpec
    name::String                    # string version of name
    tuple::Tuple#{Vararg{UInt8}}     # tuple version of name
    id::UInt32                      # id in main featSpecList
    coreF::Function                 # function that returns feature from specified granular segment
...<snipped>
end

const FeatValTypes::Type = Union{UInt8, Int8, String, Vector{UInt8}, Vector{Int8},Vector{String}}
 .... <snipped>

 vals0::FeatValTypes = f.coreF(docSens, s, t)
    

Unfortunately, that error is a bug with Cthulhu. A new version should be released soon.

The reason you have type stability is not just because the return type is unknown but also because each function has their own type so the type of coreF is unknown (Function is not a concrete type).
There are two main solutions for this, either make FeatSpec a parametric type such that its type depends on which function coreF is or use a function wrapper from yuyichao/FunctionWrappers.jl (github.com).

I would recommend the first method if the type of coreF can be inferred, i.e. if you have a Vector[FeatSpec} the coreF should be the same for all of them. If you only have a few different coreFs I would create seperate lists for each of them. Otherwise FunctionWrappers.jl will probably be better.

Thanks so much Zentrik for the response. It helps.
I have a couple further questions about your suggestions. I do have a main Vector[FeatSpec], but every entry in the vector (about 20) has its own unique function. All the functions take the same three variables as input, eg (docSens, s, t). I could group the featSpecs and parameterize them by the type of output variable, but I’m not sure that helps anything because there is generally more than one function per output type. I tried making the name of the feature as a parameter via Val{f.name}, as in coreF(Val(f.name), docSens, s, t) but that didn’t seem to make much difference in performance.
Thanks for the pointer to the function wrappers. I’ll take a look…

Sorry just to clarify for the first suggestion I meant something like

struct FeatSpec{T}
    name::String                    # string version of name
    tuple::Tuple#{Vararg{UInt8}}     # tuple version of name
    id::UInt32                      # id in main featSpecList
    coreF::T                 # function that returns feature from specified granular segment
...<snipped>
end

const FeatValTypes::Type = Union{UInt8, Int8, String, Vector{UInt8}, Vector{Int8},Vector{String}}
 .... <snipped>

 vals0::FeatValTypes = f.coreF(docSens, s, t)

But it does sound like FunctionWrappers.jl would be the right choice here.