Skip to content

Rework fitted and predict to allocate less#887

Draft
palday wants to merge 11 commits intomainfrom
pa/reworked-fitted-predicted
Draft

Rework fitted and predict to allocate less#887
palday wants to merge 11 commits intomainfrom
pa/reworked-fitted-predicted

Conversation

@palday
Copy link
Copy Markdown
Member

@palday palday commented Apr 22, 2026

With the help of ChatGPT,1 I managed to reduce a few allocations in fitted/predict. One aspect builds on some comments we already had in the code about having an effects! helper to reduce some duplicate computations. The other aspect restructures predict(newdata) so that it only allocates the actual model matrices and not A and L (and the optsum, but the impact of that is really quite trivial).

The changes focus on two areas:

  • lowering avoidable workspace churn in fitted!
  • avoiding construction of a full LinearMixedModel inside predict(newdata) when only prediction design matrices are needed

Here's how ChatGPT summarized the changes:

Code Changes

fitted! / internal effects path

  • introduced internal scratch-oriented helpers for fixed and random effects
  • routed fitted!(v, m) through explicit internal β/b workspaces
  • made the internal pivoted/truncated fixed-effects path explicit so hot paths do not depend on user-facing coefficient layout helpers
  • kept public fixef, coef, and ranef behavior unchanged

predict(newdata)

  • reduced repeated work in level matching by precomputing level-index dictionaries
  • computed old BLUPs once instead of separately in each branch
  • aligned the :error branch with the same sorted-term ordering used elsewhere in prediction
  • replaced full LinearMixedModel(f, newdata; ...) construction with a lightweight prediction-design path that only builds the fixed-effects and random-effects design terms needed for prediction

Benchmarks / docs

  • added benchmark/fitted_predict.jl for dataset/formula-driven fitted/predict benchmarking
  • added benchmark/compare_fitted_predict.jl for comparing two checkouts/worktrees
  • added benchmark/README.md documenting the benchmark scripts

Benchmark Results

Compared against pre-series baseline commit #87138de3.

sleepstudy

  • fitted!(buf, m): 2144 B -> 1520 B, 4.57 µs -> 4.24 µs
  • fitted(m): 3952 B -> 3216 B, 5.24 µs -> 4.95 µs
  • predict(m, same_data): 111704 B -> 94656 B, 105.0 µs -> 89.9 µs
  • predict(m, new_levels; new_re_levels=:population): 104840 B -> 82576 B, 96.7 µs -> 94.2 µs

kb07

  • fitted!(buf, m): 4032 B -> 2864 B, 16.49 µs -> 15.65 µs
  • fitted(m): 18816 B -> 17456 B, 18.12 µs -> 16.86 µs
  • predict(m, same_data): 1391448 B -> 1292224 B, 874.9 µs -> 722.2 µs
  • predict(m, new_subject; new_re_levels=:population): 1331104 B -> 1161840 B, 798.5 µs -> 715.8 µs

  • add entry in NEWS.md
  • after opening this PR, add a reference and run docs/NEWS-update.jl to update the cross-references.
  • I've bumped the version appropriately

Footnotes

  1. The impetus for this was a curiosity about how LLMs would perform with a large, complex codebase in a somewhat uncommon language that used some math that is, unfortunately, not as well known as it should be. So I basically asked it to audit the codebase for potential bugs and it found a few small things and suggested optimizing the allocation behavior here. I was aware of how inefficient the behavior was, but probably would have never gotten around to improving it because it's good enough for the cases I've personally encountered.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.63%. Comparing base (b451681) to head (eee21ce).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #887      +/-   ##
==========================================
+ Coverage   95.60%   95.63%   +0.03%     
==========================================
  Files          38       38              
  Lines        3708     3737      +29     
==========================================
+ Hits         3545     3574      +29     
  Misses        163      163              
Flag Coverage Δ
current 95.31% <100.00%> (+0.03%) ⬆️
minimum 95.58% <100.00%> (+0.08%) ⬆️
nightly 95.31% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant