# Why does \`JuMP.set\_objective\_coefficient(m, x, c::Float64)\` have allocations?

**URL:** <https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717>\
**Category:** Optimization (Mathematical)\
**Tags:** question, jump, gurobi\
**Created:** [February 18, 2026, 5:10am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717 "2026-02-18T05:10:24Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [February 18, 2026, 5:10am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/1 "2026-02-18T05:10:25Z")

</div>

I’m very curious here. Where can this API lead to allocations so that it has even more allocations that `@objective` (in number of allocations, though not in total memory size) when I want to reset the objective function?

@odow Could you explain the detailed procedures related to these two methods? e.g. the macro would construct a JuMP expression as an intermediate object…

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [February 18, 2026, 5:28am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/2 "2026-02-18T05:28:57Z")

</div>

It appears that you allocate an intermediate object before modification, why?

```julia-auto
struct ScalarCoefficientChange{T} <: AbstractFunctionModification
    variable::VariableIndex
    new_coefficient::T
end
function MOI.modify(
    model::Optimizer,
    ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}},
    chg::MOI.ScalarCoefficientChange{Float64},
)
    ret = GRBsetdblattrelement(
        model,
        "Obj",
        c_column(model, chg.variable),
        chg.new_coefficient,
    )
    _check_ret(model, ret)
    model.is_objective_set = true
    _require_update(model, model_change = true)
    return
end

```

People wanting to use this modification API because of their being allocation-free. If this is not the case, why do they need such an API?

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [February 18, 2026, 5:53am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/3 "2026-02-18T05:53:50Z")

</div>

Please provide a minimal reproducible example

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [February 18, 2026, 6:12am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/4 "2026-02-18T06:12:12Z")

</div>

```julia-auto
import JuMP

incre(m, x, c) = for (x, c) = zip(x, c)
    JuMP.set_objective_coefficient(m, x, c)
end

function test(m, N, c)
    JuMP.@variable(m, x[1:N])
    JuMP.@objective(m, Min, 0)
    @time incre(m, x, c)
    @time JuMP.@objective(m, Min, c'x)
end
N = 300

m = Settings.Model();
test(m, N, rand(N));
# results:
0.000150 seconds (900 allocations: 23.438 KiB)
0.000055 seconds (60 allocations: 53.250 KiB)
# The incremental method takes longer time
# And more number of allocations (900 vs 60)
# Though the 23 KiB is fewer

```

The `Settings` is a module that I wrote. You can replace with a JuMP.direct\_model(Gurobi), I guess it’s the same.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [February 18, 2026, 6:16am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/5 "2026-02-18T06:16:56Z")

</div>

I find it appears that I can use this allocation-free version of incremental modification

```julia-auto
gcc(o, x) = Gurobi.c_column(o, JuMP.index(x))
soc(o, x, c) = Gurobi.GRBsetdblattrelement(o, "Obj", gcc(o, x), c)

import Gurobi
m = Settings.Model() # a Gurobi's direct_model
o = m.moi_backend
JuMP.@variable(m, -0.5 <= x <= 1.5)
JuMP.@objective(m, Min, 0)
JuMP.unset_silent(m)
soc(o, x, -1.0) # set objective coefficient (0 allocation version)
JuMP.optimize!(m)
JuMP.value(x)
soc(o, x, 1.0) # set objective coefficient (0 allocation version)
JuMP.optimize!(m)
JuMP.value(x)

```

Although I’m not aware of its potential danger.

But I guess I’m on the right track—in practice we really need an incremental method that is allocation-free, instead of the existing one.

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [February 18, 2026, 7:15am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/6 "2026-02-18T07:15:43Z")

</div>

There’s a dynamic dispatch in `set_objective_coefficient` that depends on the objective function type. You can get around it by using the vector version:

```Julia
julia> using JuMP

julia> incre(m, x, c) = for (x, c) = zip(x, c)
           JuMP.set_objective_coefficient(m, x, c)
       end
incre (generic function with 1 method)

julia> N = 300
300

julia> c = rand(N);

julia> model = Model();

julia> @variable(model, x[1:N]);

julia> @objective(model, Min, 0)
0

julia> @time incre(model, x, c);
  0.005044 seconds (11.15 k allocations: 538.391 KiB, 96.98% compilation time)

julia> @time set_objective_coefficient(model, x, c);
  0.000085 seconds (27 allocations: 8.500 KiB)

julia> @time @objective(model, Min, c'x);
  0.000059 seconds (113 allocations: 59.844 KiB)

```

Note that some allocations when modifying an objective coefficient are irrelevant in practice.

If you need a non-allocating version, you can use MOI directly, assume a fixed objective type, etc. JuMP makes things easy from a user.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [February 18, 2026, 8:09am UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/7 "2026-02-18T08:09:45Z")

</div>

> [@odow](#):
>
> If you need a non-allocating version, you can use MOI directly, assume a fixed objective type, etc. JuMP makes things easy from a user.

Ok, now the `moisoc` function has 0 allocation

```julia-auto
julia> import JuMP

julia> m = Settings.Model(); # a Gurobi's direct_model

julia> o = JuMP.backend(m);

julia> const objtype = JuMP.MOI.ObjectiveFunction{JuMP.MOI.ScalarAffineFunction{Float64}}();

julia> moisoc(o, x, c) = JuMP.MOI.modify(o, objtype, JuMP.MOI.ScalarCoefficientChange(JuMP.index(x), c));

julia> JuMP.@variable(m, -0.5 <= x <= 1.5);

julia> JuMP.@objective(m, Min, 0);

julia> moisoc(o, x, 1.0)

julia> @time moisoc(o, x, -1.0)
  0.000009 seconds

julia> JuMP.optimize!(m)

julia> JuMP.value(x)
1.5

julia> @time JuMP.value(x)
  0.000040 seconds (10 allocations: 208 bytes)
1.5

```

I further want to eliminate the allocation of `JuMP.value`. How to achieve this?

> [@odow](#):
>
> Note that some allocations when modifying an objective coefficient are irrelevant in practice.

I care about this since I’m using multithreads and allocations would disturb the efficiency of multithreaded program due to GC overhead.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [February 18, 2026, 1:31pm UTC](https://discourse.julialang.org/t/why-does-jump-set-objective-coefficient-m-x-c-float64-have-allocations/135717/10 "2026-02-18T13:31:43Z")

</div>

@odow Thanks, I’ve learnt how to realize 0 allocation.

Here is a simple test you can take a look at the results

```julia-auto
import JuMP, Gurobi
using BenchmarkTools
const r = Ref{Float64}();
const m = Settings.Model();
const o = JuMP.backend(m);

const N = 1000;
const c = rand(N);

JuMP.@variable(m, x[1:N]);

myset(o, x, c) = for (x, c) = zip(x, c)
    Settings.setoc(o, x, c)
end;
macroset(m, x, c) = JuMP.@objective(m, Min, c'x);
myget(o, x, r) = for x = x
    Settings.value(o, x, r) > -1.0 || error()
end
jumpget(x) = for x = x
    JuMP.value(x) > -1.0 || error()
end
function jumpdot(x)
    v = JuMP.value.(x)
    for v = v
        v > -1.0 || error()
    end
end

@btime myset($o, $x, $c) # 42.442 μs (0 allocations: 0 bytes)
@btime macroset($m, $x, $c); # 124.206 μs (74 allocations: 165.95 KiB)

myset(o, x, zeros(N)); JuMP.optimize!(m)

@btime myget($o, $x, $r) # 38.061 μs (0 allocations: 0 bytes)
@btime jumpget($x) # 413.360 μs (9488 allocations: 195.12 KiB)
@btime jumpdot($x) # 422.070 μs (9490 allocations: 202.98 KiB)

```

> **my module \`Settings\`**
>
> ```julia-auto
> module Settings
> import JuMP, Gurobi
> 
> const SAF = JuMP.MOI.ObjectiveFunction{JuMP.MOI.ScalarAffineFunction{Float64}}()
> 
> _gcc(o, x) = Gurobi.c_column(o, JuMP.index(x));
> _gv!(r, o, x) = Gurobi.GRBgetdblattrelement(o, "X", _gcc(o, x), r);
> value(o, x, r) = (_gv!(r, o, x); r.x);
> setoc(o, x, c) = JuMP.MOI.modify(o, SAF, JuMP.MOI.ScalarCoefficientChange(JuMP.index(x), c))
> 
> function printinfo() 
> th = map(Threads.nthreads, (:default, :interactive))
> println("Settings> Threads=$th")
> end
> # The "Crossover=0" option is observed to be too vague (e.g. terminate with a 3% rGap) for certain cases, thus abandoned
> const C = Dict{String, Any}("OutputFlag" => 0, "Threads" => 1, "MIPGap" => 0, "MIPGapAbs" => 0, "Method" => 2)
> Env() = Gurobi.Env(C) # generate a _new_ one as defined by `Gurobi.Env`
> Model() = JuMP.direct_model(Gurobi.Optimizer(Env()))
> 
> # multi-threading
> Model(tks, v) = for i=eachindex(tks) setindex!(tks, Threads.@spawn(setindex!(v, Model(), i)), i) end
> function Model(tks)
> v = similar(tks, JuMP.Model)
> Model(tks, v)
> foreach(wait, tks)
> v
> end
> 
> # JuMP.set_objective_sense(m, JuMP.MIN_SENSE)
> 
> end
> 
> ```
