Performance tips
This tutorial was generated using Literate.jl. Download the source as a .jl
file.
By now you should have read the other "getting started" tutorials. You're almost ready to write your own models, but before you do so there are some important things to be aware of.
The Julia manual has an excellent section on Performance tips. The purpose of this tutorial is to highlight a number of performance issues that are specific to JuMP.
Required packages
This tutorial uses the following packages:
julia> using JuMP
julia> import HiGHS
Use macros to build expressions
Use JuMP's macros to build expressions.
Constructing an expression outside the macro results in intermediate copies of the expression. For example,
x[1] + x[2] + x[3]
is equivalent to
a = x[1]
b = a + x[2]
c = b + x[3]
Since we only care about c
, the a
and b
expressions are not needed and constructing them slows the program down.
JuMP's macros rewrite the expressions to operate in-place and avoid these extra copies. Because they allocate less memory, they are faster, particularly for large expressions.
Here's an example.
julia> model = Model()
A JuMP Model ├ solver: none ├ objective_sense: FEASIBILITY_SENSE ├ num_variables: 0 ├ num_constraints: 0 └ Names registered in the model: none
julia> @variable(model, x[1:3])
3-element Vector{VariableRef}: x[1] x[2] x[3]
Here's what happens if we construct the expression outside the macro:
julia> @allocated x[1] + x[2] + x[3]
1296
The @allocated
measures how many bytes were allocated during the evaluation of an expression. Fewer is better.
If we use the @expression
macro, we get many fewer allocations:
julia> @allocated @expression(model, x[1] + x[2] + x[3])
640
Use add_to_expression!
to build summations
If you don't want to use the expression macros, use add_to_expression!
to build summations. For example, instead of:
julia> expr = zero(AffExpr)
0
julia> for i in 1:3 expr += x[i] end
julia> expr
x[1] + x[2] + x[3]
do
julia> expr = zero(AffExpr)
0
julia> for i in 1:3 add_to_expression!(expr, x[i]) end
julia> expr
x[1] + x[2] + x[3]
The former is equivalent to:
julia> expr0 = zero(AffExpr)
0
julia> expr1 = expr0 + x[1]
x[1]
julia> expr2 = expr1 + x[2]
x[1] + x[2]
julia> expr = expr2 + x[3]
x[1] + x[2] + x[3]
which allocates four unique AffExpr
objects. The latter efficiently updates expr
in-place so that only one AffExpr
object is allocated.
The function add_to_expression!
also supports terms like y += a * x
where a
is a constant. For example, instead of:
julia> expr = zero(AffExpr)
0
julia> for i in 1:3 expr += i * x[i] end
julia> expr
x[1] + 2 x[2] + 3 x[3]
do
julia> expr = zero(AffExpr)
0
julia> for i in 1:3 add_to_expression!(expr, i, x[i]) end
julia> expr
x[1] + 2 x[2] + 3 x[3]
Don't do this, because i * x[i]
will allocate a new AffExpr
in each iteration:
julia> expr = zero(AffExpr)
0
julia> for i in 1:3 add_to_expression!(expr, i * x[i]) end
julia> expr
x[1] + 2 x[2] + 3 x[3]
Disable string names
By default, JuMP creates String
names for variables and constraints and passes these to the solver. The benefit of passing names is that it improves the readability of log messages from the solver (for example, "variable x has invalid bounds" instead of "variable v1203 has invalid bounds"), but for larger models the overhead of passing names can be non-trivial.
Disable the creation of String
names by setting set_string_name = false
in the @variable
and @constraint
macros, or by calling set_string_names_on_creation
to disable all names for a particular model:
julia> model = Model();
julia> set_string_names_on_creation(model, false)
julia> @variable(model, x)
_[1]
julia> @constraint(model, c, 2x <= 1)
2 _[1] ≤ 1
Note that this doesn't change how symbolic names and bindings are stored:
julia> x
_[1]
julia> model[:x]
_[1]
julia> x === model[:x]
true
But you can no longer look up the variable by the string name:
julia> variable_by_name(model, "x") === nothing
true
For more information on the difference between string names, symbolic names, and bindings, see String names, symbolic names, and bindings.