Советы по производительности
Ознакомьтесь с советами по производительности Julia
В первую очередь следует прочитать раздел Советы по производительности в руководстве по Julia. Главное правило — избегать глобальных переменных. Это особенно важно, если вы изучаете JuMP после другого языка, например MATLAB.
Проблема с задержкой перед получением первого решения
Подобно печально известной проблеме с задержкой перед построением первого графика, в JuMP есть проблема с задержкой перед получением первого решения. Эта задержка происходит из-за того, что при первом вызове кода JuMP в каждом сеансе среде Julia необходимо скомпилировать большой объем кода, связанного с задачей. Над этой проблемой ведется активная работа, но вы со своей стороны тоже можете кое-что предпринять.
Предложение 1. Не вызывайте JuMP из командной строки
В других языках вы, возможно, привыкли к такому рабочему процессу:
$ julia my_script.jl
Для JuMP это не подходит, так как каждый запуск скрипта сопряжен с задержкой вследствие компиляции. Вместо этого используйте один из рабочих процессов, предлагаемых в документации Julia.
Предложение 2. Отключайте мосты, если они не используются
В настоящее время большинство задержек вызывается механизмом мостового соединения в JuMP. Если вы используете только те ограничения, которые изначально поддерживаются решателем, то можете отключить мосты, передав аргумент add_bridges = false
в Model
.
julia> model = Model(HiGHS.Optimizer; add_bridges = false)
A JuMP Model
├ solver: HiGHS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none
Предложение 3. Используйте PackageCompiler
В качестве примера задержки вследствие компиляции рассмотрим следующую линейную программу с двумя переменными и двумя ограничениями:
using JuMP, HiGHS
model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, x >= 0)
@variable(model, 0 <= y <= 3)
@objective(model, Min, 12x + 20y)
@constraint(model, c1, 6x + 8y >= 100)
@constraint(model, c2, 7x + 12y >= 120)
optimize!(model)
open("model.log", "w") do io
print(io, solution_summary(model; verbose = true))
return
end
Сохранение задачи в model.jl
и ее вызов из командной строки приводит к следующему:
$ time julia model.jl 15.78s user 0.48s system 100% cpu 16.173 total
Очевидно, что 16 секунд — это слишком большие накладные расходы при решении такой простейшей модели. Однако задержка вследствие компиляции не зависит от размера задачи, поэтому для более крупных моделей, решение которых занимает минуты или часы, 16 дополнительных секунд может быть приемлемой потерей.
На тот случай, когда задержка вследствие компиляции неприемлема, совместимость JuMP с пакетом PackageCompiler.jl позволяет легко создать пользовательский системный образ (двоичное расширение Julia, кэширующее скомпилированный код), что существенно сокращает задержку. Пользовательский образ для нашей задачи можно создать следующим образом.
using PackageCompiler, Libdl
PackageCompiler.create_sysimage(
["JuMP", "HiGHS"],
sysimage_path = "customimage." * Libdl.dlext,
precompile_execution_file = "model.jl",
)
При запуске Julia с пользовательским образом время выполнения составляет всего 0,7 секунды вместо 16:
$ time julia --sysimage customimage model.jl 0.68s user 0.22s system 153% cpu 0.587 total
Другие настройки производительности, такие как отключение мостов или использование прямого режима, могут еще более сократить это время.
|
Построение выражений с помощью макросов
Для построения выражений используйте макросы JuMP (или add_to_expression!
). Старайтесь не создавать выражения вне макросов.
Построение выражения вне макроса приводит к созданию промежуточных копий выражения. Например:
x[1] + x[2] + x[3]
эквивалентно
a = x[1]
b = a + x[2]
c = b + x[3]
Поскольку нас интересует только c
, выражения a
и b
не нужны, а их построение замедляет программу.
Макросы JuMP переписывают выражения так, что они выполняются на месте без дополнительных копий. Благодаря выделению меньшего объема памяти они работают быстрее, особенно в случае с большими выражениями.
Вот пример.
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]
Вот что происходит, если выражение строится вне макроса:
julia> @allocated x[1] + x[2] + x[3]
69056
Макрос |
Если воспользоваться макросом @expression
, памяти будет выделяться гораздо меньше:
julia> @allocated @expression(model, x[1] + x[2] + x[3])
800
Отключение строковых имен
По умолчанию JuMP создает имена типа String
для переменных и ограничений и передает их решателю. Преимущество передачи имен заключается в более понятных сообщениях журнала решателя (например, «переменная x имеет недопустимые границы» вместо «переменная v1203 имеет недопустимые границы»), но для более крупных моделей накладные расходы на передачу имен могут выходить за рамки допустимого.
Чтобы отключить создание имен типа String
, задайте аргумент set_string_name = false
в макросах @variable
и @constraint
или вызовите set_string_names_on_creation
для отключения всех имен для определенной модели:
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
Обратите внимание, что это не меняет способ хранения символьных имен и привязок:
julia> x
_[1]
julia> model[:x]
_[1]
julia> x === model[:x]
true
Однако найти переменную по строковому имени теперь не получится:
julia> variable_by_name(model, "x") === nothing
true
Дополнительные сведения о различиях между строковыми именами, символьными именами и привязками см. в разделе Строковые имена, символьные имена и привязки. |