Skip to content

Commit 93a1abe

Browse files
committed
Fix import performance regression and update time limits
Unfortunately, #3030 doubled the import time for our performance test model, but the check there was too lax to catch it. Sometimes, `smart_subs_dict` is applied to individual expressions, sometimes to complete matrices. If it's only applied to individual expressions or small matrices, avoid the overhead of flattening the substitutions first. Update the time limits for the performance tests to catch such regressions earlier. Fixes #3048.
1 parent baed9cd commit 93a1abe

File tree

2 files changed

+52
-25
lines changed

2 files changed

+52
-25
lines changed

python/sdist/amici/importers/utils.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -426,27 +426,54 @@ def smart_subs_dict(
426426
else:
427427
s = [(eid, expr[field]) for eid, expr in subs.items()]
428428

429-
if not reverse:
430-
# counter-intuitive, but we need to reverse the order for reverse=False
431-
s.reverse()
432-
433-
with sp.evaluate(False):
434-
# The new expressions may themselves contain symbols to be substituted.
435-
# We flatten them out first, so that the substitutions in `sym` can be
436-
# performed simultaneously, which is usually more efficient than
437-
# repeatedly substituting into `sym`.
438-
# TODO(performance): This could probably be made more efficient by
439-
# combining with toposort used to order `subs` in the first place.
440-
# Some substitutions could be combined, and some terms not present in
441-
# `sym` could be skipped.
442-
for i in range(len(s) - 1):
443-
for j in range(i + 1, len(s)):
444-
if s[j][1].has(s[i][0]):
445-
s[j] = s[j][0], s[j][1].xreplace({s[i][0]: s[i][1]})
446-
447-
s = dict(s)
448-
sym = sym.xreplace(s)
449-
return sym
429+
# We have the choice to flatten the replacement expressions first or to
430+
# substitute them one by one into `sym`. Flattening first is usually
431+
# more efficient if `sym` is large (e.g., a matrix with many elements)
432+
# and `subs` is cascading (i.e., substitutions depend on other
433+
# substitutions). Otherwise, substituting one by one is usually more
434+
# efficient, because flattening scales quadratically with the number of
435+
# substitutions.
436+
# The exact threshold is somewhat arbitrary and may need to be
437+
# adjusted in the future.
438+
flatten_first = isinstance(sym, sp.MatrixBase) and sym.rows * sym.cols > 20
439+
440+
if flatten_first:
441+
if not reverse:
442+
# counter-intuitive, but on this branch, we need to reverse the
443+
# order for `reverse=False`
444+
s.reverse()
445+
446+
with sp.evaluate(False):
447+
# The new expressions may themselves contain symbols to be
448+
# substituted. We flatten them out first, so that the
449+
# substitutions in `sym` can be performed simultaneously,
450+
# which can be more efficient than repeatedly substituting into
451+
# `sym`.
452+
# TODO(performance): This could probably be made more efficient by
453+
# combining with toposort used to order `subs` in the first
454+
# place.
455+
# Some substitutions could be combined, and some terms not
456+
# present in `sym` could be skipped.
457+
for i in range(len(s) - 1):
458+
for j in range(i + 1, len(s)):
459+
if s[j][1].has(s[i][0]):
460+
s[j] = s[j][0], s[j][1].xreplace({s[i][0]: s[i][1]})
461+
462+
s = dict(s)
463+
sym = sym.xreplace(s)
464+
return sym
465+
466+
else:
467+
if reverse:
468+
s.reverse()
469+
470+
with sp.evaluate(False):
471+
for old, new in s:
472+
# note that substitution may change free symbols,
473+
# so we have to do this recursively
474+
if sym.has(old):
475+
sym = sym.xreplace({old: new})
476+
return sym
450477

451478

452479
def smart_subs(element: sp.Expr, old: sp.Symbol, new: sp.Expr) -> sp.Expr:

tests/performance/reference.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Reference wall times (seconds) with some buffer
22
create_sdist: 16
33
install_sdist: 150
4-
petab_import: 2100
5-
install_model: 120
4+
petab_import: 720
5+
install_model: 60
66
install_model_O0: 40
7-
install_model_O1: 90
8-
install_model_O2: 120
7+
install_model_O1: 45
8+
install_model_O2: 60
99
forward_simulation: 2
1010
forward_sensitivities: 2
1111
adjoint_sensitivities: 2.5

0 commit comments

Comments
 (0)