Why does RefValue has allocation?

How to explain that only by adding one line r[] I had one more allocation? I don’t understand.
You can neglect the packages, just focus on the function _grbval and grbval, and r. Thanks.

import JuMP, Gurobi
_grbval(o, x, r) = Gurobi.GRBgetdblattrelement(o, "X", gcc(o, x), r); # calling a C-API
function grbval(o, x, r)
    _grbval(o, x, r)
    getfield(r, :x)
end;

gcc(o, x) = Gurobi.c_column(o, JuMP.index(x));
const r = Ref{Float64}();
const m = JuMP.direct_model(Gurobi.Optimizer());
const o = JuMP.backend(m);
JuMP.@variable(m, x);
JuMP.optimize!(m)
_grbval(o, x, r)
grbval(o, x, r)
@allocated _grbval(o, x, r) # 0
@allocated grbval(o, x, r) # 0
@time _grbval(o, x, r) # 0.000008 seconds
@time grbval(o, x, r) # 0.000010 seconds (1 allocation: 16 bytes)

The question is: why does the last line shows there is 1 allocation 16 bytes.

The definition used in _grbval is

function GRBgetdblattrelement(model, attrname, element, valueP)
    ccall((:GRBgetdblattrelement, libgurobi), Cint, (Ptr{GRBmodel}, Ptr{Cchar}, Cint, Ptr{Cdouble}), model, attrname, element, valueP)
end

You’re more likely to get help if you make your example as self contained as possible.

You say I can neglect the packages, but they’re pretty intertwined with your example. If I just construct a simple analogue to your example though, I see no such allocations.

1 Like

Yes, I cannot reproduce with simple examples either. But mine was indeed a special case.

I think there was already a function barrier, so the _grbval function should be a black box, right?

Some related details are

should I conclude that @time is misleading, after seeing

julia> @time grbval(o, x, r)
  0.000010 seconds (1 allocation: 16 bytes)
0.0

julia> using BenchmarkTools

julia> @btime grbval($o, $x, $r)
  38.031 ns (0 allocations: 0 bytes)
0.0

?

A very old section buried in the Perfomance Tips:

julia> @time sum_arg(x)
  0.007551 seconds (3.98 k allocations: 200.548 KiB, 99.77% compilation time)
523.0007221951678

julia> @time sum_arg(x)
  0.000006 seconds (1 allocation: 16 bytes)
523.0007221951678

The 1 allocation seen is from running the @time macro itself in global scope. If we instead run the timing in a function, we can see that indeed no allocations are performed:

julia> time_sum(x) = @time sum_arg(x);

julia> time_sum(x)
  0.000002 seconds
523.0007221951678

You can manually make a forwarding method like time_sum to verify that’s the case for your code. BenchmarkTools puts the input expression in a function so it has the same effect. Note that despite the exact wording of the section, this extra allocation still shows up in let blocks and other (local) scope-introducing blocks except functions:

julia> let; @time sum_arg(x) end
  0.000007 seconds (1 allocation: 16 bytes)
500.0699540425248