Thereβs a few things going on here:
Type instabilities
Julia canβt infer the type of Cons
, which slows the code down. You can improve this using a function barrier.
https://docs.julialang.org/en/v1/manual/performance-tips/#kernel-functions
Scalarized access by JuMP
JuMP queries each element individually, instead of querying the vector.
Overheads in Gurobi.jl
Even the Gurobi.jl code at the MOI level has some overheads:
https://github.com/jump-dev/Gurobi.jl/blob/af5b927ff51d2f2df42de935cc35e27b6e52569d/src/MOI_wrapper/MOI_wrapper.jl#L3064-L3081
Solutions
If youβre in direct-mode, you can use:
indices = index.(Cons)
dual_solution .= MOI.get(backend(model), MOI.ConstraintDual(), indices)
This skips some of the JuMP checks
If youβre happy with pure Gurobi, and you know how Gurobi orders the rows in the constraint matrix you can also do
GRBgetdblattrarray(
backend(model),
"Pi",
0,
length(dual_solution),
dual_solution,
)
Hereβs my solution
using JuMP
using Gurobi
using TimerOutputs
function main(use_vectorized_update=false)
to = TimerOutput()
@timeit to "Generate a random, sparse linear optimization problem" begin
m = 3000
n = 6000
A = zeros(m, n)
for j=1:n
entries = rand(1:m, 2)
A[entries[1],j] = rand()
A[entries[2],j] = rand()
end
b = ones(m)
c = ones(n)
end
@timeit to "Initialize the restricted master problem (RMP)" begin
dual_solution = zeros(m)
k = 1
model = direct_model(Gurobi.Optimizer())::JuMP.Model
set_silent(model)
@variable(model, 0 <= x[1:k] <= 1)
@constraint(model, Cons[i = 1:m], sum(A[i, j] * x[j] for j in 1:k) <= b[i])
@objective(model, Max, sum(c[j] * x[j] for j in 1:k))
set_optimizer_attribute(model, "Method", 0)
end
return main(use_vectorized_update, to, model, dual_solution, Cons, A, c)
end
function main(use_vectorized_update, to, model, dual_solution, Cons, A, c)
indices = index.(Cons)
m, n = size(A)
for k in 2:n
@timeit to "Solve restricted master problem using primal simplex" begin
optimize!(model)
end
if use_vectorized_update
@timeit to "Extract the optimal dual solution (vectorized)" begin
dual_solution .= MOI.get(backend(model), MOI.ConstraintDual(), indices)
end
else
@timeit to "Extract the optimal dual solution (non-vectorized)" begin
GRBgetdblattrarray(
backend(model),
"Pi",
0,
length(dual_solution),
dual_solution,
)
end
end
@timeit to "Add new column to restricted master problem" begin
new_var = @variable(
model,
base_name = "x",
lower_bound = 0,
upper_bound = 1,
)
set_objective_coefficient(model, new_var, c[k])
for i = 1:m
if A[i, m] != 0
set_normalized_coefficient(Cons[i], new_var, A[i, k])
end
end
end
end
to, model
end
@time to, model = main(true)
@time to, model = main(false)
This drops things down to
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Time Allocations
ββββββββββββββββββββββ βββββββββββββββββββββββ
Tot / % measured: 5.26s / 99.3% 148MiB / 100%
Section ncalls time %tot avg alloc %tot avg
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Solve restricted master prob... 6.00k 5.05s 96.6% 841ΞΌs 3.30MiB 2.23% 576B
Add new column to restricted... 6.00k 83.4ms 1.60% 13.9ΞΌs 2.32MiB 1.57% 406B
Generate a random, sparse li... 1 66.2ms 1.27% 66.2ms 138MiB 93.8% 138MiB
Extract the optimal dual sol... 6.00k 21.2ms 0.41% 3.54ΞΌs 93.7KiB 0.06% 16.0B
Initialize the restricted ma... 1 6.84ms 0.13% 6.84ms 3.44MiB 2.33% 3.44MiB
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ