What does @_## mean in @code_warntype (and other questions)?

Hello,

I’m a very basic Julia user working on a model simulating a food web and fishing. The model works but I need to decrease the run time. I’ve read several tutorials about @code_warntype and I understand I should define types where possible, to not change them within a function, and to not mix types.

However, the MWEs do not answer my questions. I understand they are supposed to be minimal. I have tried creating a MWE, but I am not so skilled. I have some nested functions.

This is how I understand the output of @code_warntype - please correct me if I am wrong. My function is called generating_pristine.

MethodInstance - this first states the type of parameters, and then the name of the parameters used in a function

Arguments - these are the named parameters together with their type (coloured) used in the function
The first line is #self#::Core.Const(generating_pristine) - what does this mean?

  index_vessel@_11::Int64
  index_clearance@_12::Int64
  index_extinction_parameter::Int64       
  index_callback_pristine::Vector{Float64}

What does @_11 or @_12 mean? Why are not all parameters given a number?

Locals - I think these are objects within the function, that include the named parameters

 @_22::Union{Nothing, Tuple{Tuple{Int64, Any}, Tuple{Int64, Any}}}
#49::var"#49#52"
@_24::Int64
file_name_pristine::String

Why are some objects numbered? Do they refer to the code line or object compiled? Or the nth time a parameter is called?

Body - I think this is where the calculations get done

 %94  = Base.indexed_iterate(%36, 20, @_24::Core.Const(20))::Core.PartialStruct(Tuple{Any, Int64}, Any[Any, Core.Const(21)])
           (trophic_position = Core.getfield(%94, 1))
           (@_24 = Core.getfield(%94, 2))
 %97  = Base.indexed_iterate(%36, 21, @_24::Core.Const(21))::Core.PartialStruct(Tuple{Any, Int64}, Any[Any, Core.Const(22)])
 %98  = Core.getfield(%97, 1)::Any
           Core.setfield!(num_nutrient@_94::Core.Box, :contents, %98)
           (@_24 = Core.getfield(%97, 2))

Is %94 like the 94th time step while running the function? I know I need to focus on the ::Any, but I don’t understand what part of the code or to what parameter/object it is referring to.

I have a lot of ::Any, but I don’t understand why as I have set the type for index_vessel earlier, shown by index_vessel@_11::Int64 in the Arguments.

%198 = Core.getfield(index_vessel@_100::Core.Box, :contents)::Any
│    %199 = (%198 - 1)::Any
│    %200 = (%192:%199)::Any
│    %201 = Base.getindex(%185, %200)::Any
│    %202 = Base.broadcasted(Main.:<=, %201, 0.0)::Any
│    %203 = Base.materialize(%202)::Any
│    %204 = Main.sum(%203)::Any
│    %205 = Base.convert(Main.Int64, %204)::Any
│           (extinct_pristine = Core.typeassert(%205, Main.Int64))
│    %207 = extinct_pristine::Int64
│    %208 = Core.isdefined(num_nutrient@_94::Core.Box, :contents)::Bool

I thought if you define a parameter, and pass it into a function, it should keep the same data and type.

I’m feeling quite lost in this. Any help is appreciated.

These are the references I have used
https://docs.julialang.org/en/v1/manual/performance-tips/
https://tutorials.sciml.ai/html/introduction/03-optimizing_diffeq_code.html
https://techytok.com/code-optimisation-in-julia/
https://book.sciml.ai/notes/02/
https://www.cs.purdue.edu/homes/hnassar/JPUG/performance.html
https://m3g.github.io/JuliaNotes.jl/stable/instability/

Just to be sure: did you check with a profiler first?

I ran @profile but I don’t understand how to read it either. What specifically am I looking for?
It is quite long, but I know it shows how many allocations are needed for certain parts. Here is the first part

=========================================================
     ╎44245 @Base\client.jl:495; _start()
     ╎ 44245 @Base\client.jl:309; exec_options(opts::Base.JLOptions)
     ╎  44245 @Base\client.jl:379; run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool)
     ╎   44245 @Base\essentials.jl:714; invokelatest
     ╎    44245 @Base\essentials.jl:716; #invokelatest#2
     ╎     44245 @Base\client.jl:394; (::Base.var"#930#932"{Bool, Bool, Bool})(REPL::Module)
     ╎    ╎ 44245 ...package_win64\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:351; run_repl(repl::REPL.AbstractREPL, consumer::Any)
     ╎    ╎  44245 ...package_win64\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:364; run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool)
     ╎    ╎   44245 ...ackage_win64\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:231; start_repl_backend(backend::REPL.REPLBackend, consumer::Any)
     ╎    ╎    44245 ...ackage_win64\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:246; repl_backend_loop(backend::REPL.REPLBackend)
     ╎    ╎     44245 ...ackage_win64\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:150; eval_user_input(ast::Any, backend::REPL.REPLBackend)
     ╎    ╎    ╎ 44245 @Base\boot.jl:373; eval
     ╎    ╎    ╎  44245 @Atom\src\repl.jl:216; evalrepl(mod::Module, line::String)
     ╎    ╎    ╎   44245 @Base\logging.jl:623; with_logger
     ╎    ╎    ╎    44245 @Base\logging.jl:511; with_logstate(f::Function, logstate::Any)
     ╎    ╎    ╎     44245 @Atom\src\repl.jl:228; (::Atom.var"#258#260"{Module})()
     ╎    ╎    ╎    ╎ 44245 @Atom\src\repl.jl:198; repleval(mod::Module, line::Expr)
     ╎    ╎    ╎    ╎  44245 @Base\boot.jl:373; eval
     ╎    ╎    ╎    ╎   44241 untitled-403d8f52abe991641ade3c3e069763e9:55; generating_pristine(Ropt::Float64, γ::Float64, num_nutrient::Int64, num_plant::Int64, num_a...
     ╎    ╎    ╎    ╎    44241 @DiffEqBase\src\solve.jl:162; (::CommonSolve.var"#solve##kw")(::NamedTuple{(:abstol, :reltol, :callback), Tuple{Float64,...
     ╎    ╎    ╎    ╎     44241 @DiffEqBase\src\solve.jl:168; #solve#40
     ╎    ╎    ╎    ╎    ╎ 44241 @DiffEqBase\src\solve.jl:173; solve_up##kw
     ╎    ╎    ╎    ╎    ╎  44241 @DiffEqBase\src\solve.jl:182; solve_up(prob::ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, Tuple{Vector{F...
     ╎    ╎    ╎    ╎    ╎   44241 @DiffEqBase\src\solve.jl:142; solve_call##kw
     ╎    ╎    ╎    ╎    ╎    44241 @DiffEqBase\src\solve.jl:155; #solve_call#39
     ╎    ╎    ╎    ╎    ╎     44241 @Sundials\src\common_interface\solve.jl:14; __solve##kw
     ╎    ╎    ╎    ╎    ╎    ╎ 44241 @Sundials\src\common_interface\solve.jl:14; __solve##kw
     ╎    ╎    ╎    ╎    ╎    ╎  44241 @Sundials\src\common_interface\solve.jl:14; __solve##kw
     ╎    ╎    ╎    ╎    ╎    ╎   44241 @Sundials\src\common_interface\solve.jl:14; __solve##kw
     ╎    ╎    ╎    ╎    ╎    ╎    44241 @Sundials\src\common_interface\solve.jl:14; __solve##kw
     ╎    ╎    ╎    ╎    ╎    ╎     13    @Sundials\src\common_interface\solve.jl:14; __solve(prob::ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, Tuple{Vecto...
     ╎    ╎    ╎    ╎    ╎    ╎    ╎ 13    @Sundials\src\common_interface\solve.jl:123; (::SciMLBase.var"#__init##kw")(::NamedTuple{(:abstol, :reltol, :callback), Tuple{Fl...
     ╎    ╎    ╎    ╎    ╎    ╎    ╎  13    @Sundials\src\common_interface\solve.jl:400; __init(prob::ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, Tuple{Vect...
     ╎    ╎    ╎    ╎    ╎    ╎    ╎   13    @SciMLBase\src\scimlfunctions.jl:345; ODEFunction
     ╎    ╎    ╎    ╎    ╎    ╎    ╎    4     ...Julia WD\phd-julia-code\Function running fisheries.jl:82; dBdT(dB::Vector{Float64}, bioS::Vector{Float64}, p::Tuple{Vector{Float64}, Float6...
    1╎    ╎    ╎    ╎    ╎    ╎    ╎     1     ...Julia WD\phd-julia-code\Function running fisheries.jl:6; FF(num_nutrient::Int64, bm::Vector{Float64}, foodWeb::Matrix{Float64}, pred::Int6...
     ╎    ╎    ╎    ╎    ╎    ╎    ╎     3     ...Julia WD\phd-julia-code\Function running fisheries.jl:11; FF(num_nutrient::Int64, bm::Vector{Float64}, foodWeb::Matrix{Float64}, pred::Int6...
    3╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎ 3     @Base\math.jl:907; ^
     ╎    ╎    ╎    ╎    ╎    ╎    ╎    5     ...Julia WD\phd-julia-code\Function running fisheries.jl:107; dBdT(dB::Vector{Float64}, bioS::Vector{Float64}, p::Tuple{Vector{Float64}, Float6...        
     ╎    ╎    ╎    ╎    ╎    ╎    ╎     5     ...Julia WD\phd-julia-code\Function running fisheries.jl:11; FF(num_nutrient::Int64, bm::Vector{Float64}, foodWeb::Matrix{Float64}, pred::Int6...
    1╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎ 1     @Base\float.jl:405; *
    4╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎ 4     @Base\math.jl:907; ^
     ╎    ╎    ╎    ╎    ╎    ╎    ╎    4     ...Julia WD\phd-julia-code\Function running fisheries.jl:116; dBdT(dB::Vector{Float64}, bioS::Vector{Float64}, p::Tuple{Vector{Float64}, Float6...        
     ╎    ╎    ╎    ╎    ╎    ╎    ╎     4     ...Julia WD\phd-julia-code\Function running fisheries.jl:11; FF(num_nutrient::Int64, bm::Vector{Float64}, foodWeb::Matrix{Float64}, pred::Int6...        
    4╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎ 4     @Base\math.jl:907; ^

Here is how I do it.

They are given a number when you have multiple variables with the same name. For example:

julia> f(x, y) = (x = 2; x+y)
f (generic function with 2 methods)

julia> @code_warntype f(3, 2)
MethodInstance for f(::Int64, ::Int64)
  from f(x, y) in Main at REPL[23]:1
Arguments
  #self#::Core.Const(f)
  x@_2::Int64

The meaning of the number itself is a bit technical but it is basically an index which is used to identify various properties about the variable.

It is called an “SSA value”. You can read a bit about it here Static single-assignment form - Wikipedia. It is basically a variable that is only assigned to once.

Looking at Core.getfield(index_vessel@_100::Core.Box, :contents) it seems you have a Box which can happen if you for example do something like:

julia> function f(x::Int)
           g() = x += 1
           return g()
       end
f (generic function with 3 methods)

julia> @code_warntype f(2)
MethodInstance for f(::Int64)
  from f(x::Int64) in Main at REPL[30]:1
Arguments
  #self#::Core.Const(f)
  x@_2::Int64
Locals
  g::var"#g#1"
  x@_4::Union{Int64, Core.Box}
Body::Any
1 ─      (x@_4 = x@_2)
│        (x@_4 = Core.Box(x@_4::Int64))
│        (g = %new(Main.:(var"#g#1"), x@_4::Core.Box))
│   %4 = (g)()::Any
└──      return %4

(rebind a variable inside another function).

1 Like

This looks neat!

A lot of these bars seem big. Should I try looking at the red parts (allocating)?

For some things, I preallocate the size and pass this as an argument to create the object, for example v
For others, I define it within the function. Which one is correct?

const num_nutrient = 2

function create_vectors()
    v = zeros(num_nutrient)
    v::Vector{Float64} = create_v(num_nutrient,v)
end

function create_v(num_nutrient,v)
    v[1] = 1.0
    v[2] = 0.5
    return v
end

Is this the correct way?

I don’t see any significant allocation or dynamic dispatch in the image you posted. You can click one of the bigger bars and should then see were the time is spent in your source code or in one of the included packages.

None of those should be related. I think it is easiest if you can show a representative piece of code that has the bad @code_warntype behavior.

I agree it would be easiest to show the bad code, but I don’t know how to identify it using @code_warntype.

Using Juno.profiler() as suggested by @goerch has let me find the functions and lines of code that take a lot of time. I will assume these lines are causing the bad code messages in @code_warntype.

This is my attempt at a MWE. foo3 is not what I expected, but the problematic lines are commented. I think these lines must take up a lot of resources while computing.

const num_nutrient = 2
const num_plant = 30
const index_food_web = num_nutrient+num_plant

function bigger_function(num_nutrient,num_plant,index_food_web)
    function create_vec_parameter2()
        q_one = rand(Normal(0.5,0.2))+1.0 
        bm = rand(index_food_web)
        bioS = rand(index_food_web)
        foodWeb = rand(index_food_web, index_food_web)
        b = rand(size(foodWeb))
        h = rand(size(foodWeb))
        ω = zeros(length(bm))
        return q_one,bm,bioS,foodWeb,b,h,ω
    end
    
    q_one,bm,bioS,foodWeb,b,h,ω = create_vec_parameter2()
    
    function FF2(num_nutrient, bm, foodWeb, pred, prey, b,bioS, h, ω, q_one)
        sumF = 0.0
        for k in num_nutrient+1:length(bm)
            if foodWeb[k, pred] !== 0.0
                sumF  = sumF+b[k, pred]*abs(bioS[k])^q_one #this is a problem line
            end
        end
        return (((ω[pred]*b[prey, pred])^q_one)/(ω[pred]*h[pred]*sumF))/bm[pred]
    end
    
    function one_more_function(num_nutrient,num_plant,index_food_web,bioS,bm,b, h, ω, q_one)
        foo = 0.0
        for j in (num_nutrient+num_plant+1):(index_food_web)
            foo = foo + bioS[j]*FF2(num_nutrient, bm, foodWeb, pred, prey, b,bioS, h, ω, q_one) #this is a bigger problem line
        end
        return foo
    end
    
    foo2 = one_more_function(num_nutrient,num_plant,index_food_web,bioS,bm,b, h, ω, q_one)

end

foo3 = bigger_function(num_nutrient,num_plant,index_food_web)

There are multiple issues with the code:

You need to assign the return values to variables, for example

  • q_one,bm,bioS,foodWeb,b,h,ω = create_vec_parameter2()
  • c is not defined in the call to one_more_function.
  • FF2 is not called with the correct number of arguments (bioS seems to be missing).

Thank you for your reply. I have edited my MWE.

pred is now not defined in the call to FF2.

It does not show up as an error for me - I wonder why.

Are you doing that thing that lets you run the code line by line?

The loop range num_nutrient+1:length(bm) is empty so FF2 never runs. For example, try add a print call to it and you will see it never executes.

Well, this MWE is embarrassing. I guess I’ll withdraw my post/question then.

Thank you to everyone who replied.

Well, you don’t have to, unless everything is fixed after the missing argument. The MWE can just be fixed and then we can keep looking at it.

I realised I needed a double loop in one_more_function(), but FF2 still does not run. I don’t know what to do.

const num_nutrient = 2
const num_plant = 30
const index_food_web = num_nutrient+num_plant

function bigger_function(num_nutrient,num_plant,index_food_web)
    function create_vec_parameter2()
        q_one = rand(Normal(0.5,0.2))+1.0 
        bm = rand(index_food_web)
        bioS = rand(index_food_web)
        foodWeb = rand(index_food_web, index_food_web)
        b = rand(size(foodWeb))
        h = rand(size(foodWeb))
        ω = zeros(length(bm))
        return q_one,bm,bioS,foodWeb,b,h,ω
    end
    
    q_one,bm,bioS,foodWeb,b,h,ω = create_vec_parameter2()
    
    function FF2(num_nutrient, bm, foodWeb, pred, prey, b,bioS, h, ω, q_one)
        sumF = 0.0
        for k in num_nutrient+1:length(bm)
            if foodWeb[k, pred] !== 0.0
                sumF  = sumF+b[k, pred]*abs(bioS[k])^q_one #this is a problem line
            end
        end
        return (((ω[pred]*b[prey, pred])^q_one)/(ω[pred]*h[pred]*sumF))/bm[pred]
    end
    
    function one_more_function(num_nutrient,num_plant,index_food_web,bioS,bm,b, h, ω, q_one)
        foo = 0.0
        for i in (num_nutrient+num_plant+1):(index_food_web), j in (num_nutrient+num_plant+1):(index_food_web)
            foo = foo + bioS[j]*FF2(num_nutrient, bm, foodWeb, pred, prey, b,bioS, h, ω, q_one) #this is a bigger problem line
        end
        return foo
    end
    
    foo2 = one_more_function(num_nutrient,num_plant,index_food_web,bioS,bm,b, h, ω, q_one)

end

foo3 = bigger_function(num_nutrient,num_plant,index_food_web)

You have

const index_food_web = num_nutrient+num_plant

so

(num_nutrient+num_plant+1):(index_food_web)

is equal to

(num_nutrient+num_plant+1):(num_nutrient+num_plant)

which in this case is 33:32 which is an empty range. What you should do is hard to say. What do you actually want to loop over?

1 Like