Skip to content

Conversation

odow
Copy link
Member

@odow odow commented Sep 3, 2025

x-ref #2829

@odow odow marked this pull request as draft September 3, 2025 22:12
@odow
Copy link
Member Author

odow commented Sep 3, 2025

These cut 100k method instances. Still need to benchmark properly.

julia> length(after)
262910

::Type{S},
) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet}
@nospecialize(model::AbstractModel),
@nospecialize(F::Type{<:MOI.AbstractFunction}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one may be more dangerous

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the inner method will be compiled. It's just this one that isn't.

@odow
Copy link
Member Author

odow commented Sep 5, 2025

With lots of bridges

Slower because GC runs.

Before

julia> using JuMP, Gurobi, BenchmarkTools

julia> const ENV = Gurobi.Env()
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
WLS license 722777 - registered to JuMP Development
Gurobi.Env(Ptr{Nothing} @0x000000012a809400, false, 0)

julia> function main(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], 0 <= i * x[i] <= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main (generic function with 1 method)

julia> @benchmark(main(100_000); setup = GC.gc())
BenchmarkTools.Trial: 16 samples with 1 evaluation per sample.
 Range (min  max):  174.960 ms  216.162 ms  ┊ GC (min  max): 0.00%  18.86%
 Time  (median):     176.379 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   180.940 ms ±  12.739 ms  ┊ GC (mean ± σ):  2.60% ±  6.04%

   ██                                                            
  ▄██▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▁▁▁▁▁▁▁▄ ▁
  175 ms           Histogram: frequency by time          216 ms <

 Memory estimate: 344.67 MiB, allocs estimate: 6303323.

After

julia> using JuMP, Gurobi, BenchmarkTools

julia> const ENV = Gurobi.Env()
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
WLS license 722777 - registered to JuMP Development
Gurobi.Env(Ptr{Nothing} @0x0000000127ddd000, false, 0)

julia> function main(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], 0 <= i * x[i] <= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main (generic function with 1 method)

julia> @benchmark(main(100_000); setup = GC.gc())
BenchmarkTools.Trial: 14 samples with 1 evaluation per sample.
 Range (min  max):  213.427 ms  235.628 ms  ┊ GC (min  max): 14.28%  19.73%
 Time  (median):     229.635 ms               ┊ GC (median):    19.44%
 Time  (mean ± σ):   228.419 ms ±   5.341 ms  ┊ GC (mean ± σ):  18.89% ±  1.57%

  ▁                    ▁                 █▁ █   █ ██          ▁  
  █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██▁█▁▁▁█▁██▁▁▁▁▁▁▁▁▁▁█ ▁
  213 ms           Histogram: frequency by time          236 ms <

 Memory estimate: 349.26 MiB, allocs estimate: 6503956.

Without bridges

No change.

Before

julia> function main2(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], i * x[i] >= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main2 (generic function with 1 method)

julia> @benchmark(main2(100_000); setup = GC.gc())
BenchmarkTools.Trial: 20 samples with 1 evaluation per sample.
 Range (min  max):  117.079 ms  120.962 ms  ┊ GC (min  max): 0.00%  0.00%
 Time  (median):     119.386 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   119.362 ms ± 868.501 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▁                    ▁  █▁    ▁▁█  ▁ █  ▁▁ ▁▁   ▁ ▁    ▁    ▁  
  █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁██▁▁▁▁███▁▁█▁█▁▁██▁██▁▁▁█▁█▁▁▁▁█▁▁▁▁█ ▁
  117 ms           Histogram: frequency by time          121 ms <

 Memory estimate: 263.94 MiB, allocs estimate: 4802694.

After

julia> function main2(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], i * x[i] >= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main2 (generic function with 1 method)

julia> @benchmark(main2(100_000); setup = GC.gc())
BenchmarkTools.Trial: 20 samples with 1 evaluation per sample.
 Range (min  max):  117.935 ms  124.710 ms  ┊ GC (min  max): 0.00%  0.00%
 Time  (median):     121.387 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   121.877 ms ±   1.833 ms  ┊ GC (mean ± σ):  0.00% ± 0.00%

                         ▃  ▃  █                              ▃  
  ▇▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▇▁▁▁▁▁▇█▁▁█▁▁█▁▁▇▇▁▇▁▁▁▁▁▁▁▁▇▁▁▁▁▁▇▁▇▇▁▁▁▁▁▇█ ▁
  118 ms           Histogram: frequency by time          125 ms <

 Memory estimate: 263.94 MiB, allocs estimate: 4802694.

@odow
Copy link
Member Author

odow commented Sep 5, 2025

There doesn't seem to be any difference in the test runtime of master and this PR:
https://github.com/jump-dev/MathOptInterface.jl/actions/runs/17447865784/job/49546495222?pr=2830
https://github.com/jump-dev/MathOptInterface.jl/actions/runs/17505641833/job/49728541780

So maybe they're such tiny methods that it doesn't really matter.

@odow
Copy link
Member Author

odow commented Sep 5, 2025

I guess I should try: https://timholy.github.io/SnoopCompile.jl/dev/tutorials/pgdsgui/. I remember looking at it a couple of years ago without much obvious success.

@odow
Copy link
Member Author

odow commented Sep 5, 2025

So this has worked. I just needed to measure the first call, rather than subsequent. Because most of the benefit is avoiding the first compilation time.

Before

julia> using JuMP, Gurobi

julia> const ENV = Gurobi.Env()
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
WLS license 722777 - registered to JuMP Development
Gurobi.Env(Ptr{Nothing} @0x0000000114808c00, false, 0)

julia> function main(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], 0 <= i * x[i] <= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main (generic function with 1 method)

julia> @time main(100_000)
  1.185921 seconds (9.03 M allocations: 480.946 MiB, 13.67% gc time, 72.35% compilation time)
A JuMP Model
├ solver: Gurobi
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 100000
├ num_constraints: 100000
│ └ AffExpr in MOI.Interval{Float64}: 100000
└ Names registered in the model
  └ :c, :x

After

julia> using JuMP, Gurobi, BenchmarkTools

julia> const ENV = Gurobi.Env()
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
WLS license 722777 - registered to JuMP Development
Gurobi.Env(Ptr{Nothing} @0x000000013f0d4c00, false, 0)

julia> function main(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], 0 <= i * x[i] <= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main (generic function with 1 method)

julia> @time main(100_000)
  0.779390 seconds (8.10 M allocations: 427.501 MiB, 14.36% gc time, 61.28% compilation time)
A JuMP Model
├ solver: Gurobi
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 100000
├ num_constraints: 100000
│ └ AffExpr in MOI.Interval{Float64}: 100000
└ Names registered in the model
  └ :c, :x

Before

image

After

image

Code

using JuMP, Gurobi, BenchmarkTools
const ENV = Gurobi.Env()
function main(n)
    model = Model(() -> Gurobi.Optimizer(ENV))
    set_silent(model)
    @variable(model, x[1:n])
    @constraint(model, c[i in 1:n], 0 <= i * x[i] <= i)
    MOI.Utilities.attach_optimizer(model)
    return model
end
using SnoopCompileCore
tinf = @snoop_inference main(100_000);
using Profile
@profile main(100_000);
using SnoopCompile, PyPlot
mref, ax = pgdsgui(tinf);

@odow
Copy link
Member Author

odow commented Sep 5, 2025

Even without bridges this is much better.

Before

julia> using JuMP, Gurobi

julia> const ENV = Gurobi.Env()
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
WLS license 722777 - registered to JuMP Development
Gurobi.Env(Ptr{Nothing} @0x0000000142a8ac00, false, 0)

julia> function main2(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], i * x[i] >= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main2 (generic function with 1 method)

julia> @time main2(100_000)
  0.338462 seconds (4.99 M allocations: 273.114 MiB, 26.99% gc time, 38.53% compilation time)
A JuMP Model
├ solver: Gurobi
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 100000
├ num_constraints: 100000
│ └ AffExpr in MOI.GreaterThan{Float64}: 100000
└ Names registered in the model
  └ :c, :x

After

julia> using JuMP, Gurobi

julia> const ENV = Gurobi.Env()
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
WLS license 722777 - registered to JuMP Development
Gurobi.Env(Ptr{Nothing} @0x0000000118251000, false, 0)

julia> function main2(n)
           model = Model(() -> Gurobi.Optimizer(ENV))
           set_silent(model)
           @variable(model, x[1:n])
           @constraint(model, c[i in 1:n], i * x[i] >= i)
           MOI.Utilities.attach_optimizer(model)
           return model
       end
main2 (generic function with 1 method)

julia> @time main2(100_000)
  0.228506 seconds (4.84 M allocations: 265.752 MiB, 42.85% gc time, 6.42% compilation time)
A JuMP Model
├ solver: Gurobi
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 100000
├ num_constraints: 100000
│ └ AffExpr in MOI.GreaterThan{Float64}: 100000
└ Names registered in the model
  └ :c, :x

@odow odow marked this pull request as ready for review September 8, 2025 04:37
@odow
Copy link
Member Author

odow commented Sep 12, 2025

Thoughts @blegat?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants