Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document cost of named variables and constraints in Performance Tips #2973

Closed
nickrobinson251 opened this issue May 6, 2022 · 4 comments · Fixed by #2978
Closed

Document cost of named variables and constraints in Performance Tips #2973

nickrobinson251 opened this issue May 6, 2022 · 4 comments · Fixed by #2978

Comments

@nickrobinson251
Copy link
Contributor

When building a model with millions of variables and constraints, I see model build time roughly half when switching to using anonymous variables and constraints.

An example model has size

Variables: 2574376 
Objective function type: AffExpr
`AffExpr`-in-`EqualTo{Float64}`: 1097232 constraints
`AffExpr`-in-`GreaterThan{Float64}`: 315984 constraints
`AffExpr`-in-`LessThan{Float64}`: 1445558 constraints
`VariableRef`-in-`GreaterThan{Float64}`: 1721368 constraints
`VariableRef`-in-`LessThan{Float64}`: 40032 constraints
`VariableRef`-in-`ZeroOne`: 40032 constraints 

And an example code change would be:

-        @variable(model, foo[z in zones, t in times] >= 0)
+        model[:foo] = foo = @variable(model, [z in zones, t in times], lower_bound=0)

Changing all @variable and @constraint calls similarly, I see differences in model build time like below:

time allocs mem
names 36.214 s 131.82 M 7.19 GiB
no names 16.652 s 67.09 M 4.05 GiB
relative 2.175x 1.965x 1.775x

Depending on the model we're building, the time speed up varies (~1.3 - 2x) but the reduction of allocations is pretty consistently about a ~2x reduction.

The different types of "names" and the behaviour of the macros regarding names is documented

So actually making this change was fairly easy. And this discovery is a very welcome performance improvement!

However, i don't think this significant performance consideration is mentioned anywhere in the docs. And in particular it does't seem to be mentioned in the Performance Tips

I think the overhead of named variables/constraints is known (xref #2817 (comment)), so I think worth documenting given it can be a significant build-time cost and is avoidable with relatively small user code changes.

(p.s. i'm opening this just before some time off so may not be quick to respond)

@odow
Copy link
Member

odow commented May 6, 2022

Yeah, it's insane that storing a name in a dictionary can have this impact. I've been hesitant to document because it feels like it should be something we can fix, but I've had a few looks without success.

Perhaps we need a base_name = nothing option to explicitly opt-out of setting String names for things?

Then you could write @variable(model, foo[z in zones, t in times] >= 0, base_name = nothing)

@nickrobinson251
Copy link
Contributor Author

Perhaps we need a base_name = nothing option to explicitly opt-out of setting String names for things?

Then you could write @variable(model, foo[z in zones, t in times] >= 0, base_name = nothing)

Yeah, i like that idea for a few reasons:

  • it'd make it much easier to update code to use / not used names (just set / remove base_name = ...)
  • relatedly, it'd make it easy for packages to expose the choice to users (for development/debugging purposes) e.g. packages could write variables like @variable(model, foo[z in zones, t in times] >= 0, base_name = use_names ? "foo" : nothing)
    • aside: perhaps this use-case motivates base_name = false over base_name = nothing for disabling names, so that this could just written as @variable(model, foo[z in zones, t in times] >= 0, base_name = use_names), which does seem less repetitive given foo is already written.
  • This (I'm guessing) would allow a way to disable names, while still creating a binding to a Julia variable

The imagined behaviour i'm talking about in the last point is:

julia> @variable(model, foo[z in 1:2, t in 1:3], base_name=nothing));

julia> foo
2×3 Matrix{VariableRef}:
 _[1]  _[3]  _[5]
 _[2]  _[4]  _[6]
 
 julia> model[:foo] == foo
true

avoiding the model[:foo] = foo = @variable(...) part of the change mentioned in the first post.

@odow
Copy link
Member

odow commented May 7, 2022

This (I'm guessing) would allow a way to disable names, while still creating a binding to a Julia variable

Yes, exactly. All it would do is avoid the VariableName attribute.

@joaquimg
Copy link
Member

joaquimg commented May 8, 2022

I also prefer "base_name" taking a Bool instead of a Nothing.
So that the user code is cleaner, otherwise there will be a bunch o ternary operators floating around and the user has to write the variable name twice.
An additional kwarg also seems reasonable. For example "ignore_name".

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

Successfully merging a pull request may close this issue.

3 participants