Repository navigation
Keep compiling and garbage collection off the worker threads of mixed models - #65
Merged
Merged
Conversation
parashardhapola
force-pushed
the
38b-combine-studies-guides
branch
from
October 26, 2026 23:14
5f5ed6d to
b6f80ac
Compare
parashardhapola
changed the base branch from
38b-combine-studies-guides
to
master
October 26, 2026 23:18
… models The mixed-model fits and Kenward and Roger's adjustment run their compiled routines on Python threads. A garbage collection that starts on one of those threads runs the finalizers of what it frees there, and some objects may only be finalized on the thread that made them: Tk's, which matplotlib's interactive backend makes, raised errors in the test suite, and a cold compile on a worker thread once crashed the interpreter in the collector. The shared helper by_runs now runs the first run on the calling thread, so the routine compiles there, and pauses the collector while the workers run. The tests draw figures with matplotlib's Agg backend, as CI does.
parashardhapola
marked this pull request as ready for review
October 26, 2026 23:18
parashardhapola
force-pushed
the
55-threads-and-garbage-collection
branch
from
October 26, 2026 23:18
45b1bb4 to
9bb9150
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The mixed-model fits and Kenward and Roger's adjustment run their compiled routines on a
pool of Python threads. When Python's garbage collector starts on one of those threads,
it runs the finalizers of whatever it frees there, and some objects may only be finalized
on the thread that made them. The test suite showed it: on Python 3.14, Tk objects left
by matplotlib's interactive backend were finalized during a mixed-model fit and raised
"main thread is not in main loop". Once, a cold compile on a worker thread crashed the
interpreter inside the collector. Both routines now compile on the calling thread, and
the collector waits until the workers finish.
Changes
cytepool._jit.by_runs(run, n_items)(private): runs a routine that releases the GILover runs of consecutive items, the first run on the calling thread (which compiles
the routine, or loads it from the cache) and the others on a pool of Python threads,
with the garbage collector paused until they finish and restored even when a run
raises.
fit_mixed_model(through its private_fit) and Kenward and Roger's adjustment incytepool.stats._fixed_effect_inferenceuse it in place of their own thread pools.Each feature is computed by the same operations as before, so every result is
unchanged to the bit.
tests/conftest.py: the tests draw figures with matplotlib's Agg backend, as on CI,instead of the interactive backend matplotlib chooses where a display is available.
Design notes
thread removes the compile from the workers, but a collection can still start on a
worker at any allocation, such as the arguments of each call. Pausing it for the
length of the pool keeps every finalizer on the calling thread. The workers allocate
almost nothing that needs collecting, so memory does not grow.
to running it before the pool starts is small.
prange) run in native threads without Python state, sothe routines that use them are not affected.
Tests
tests/test_jit.pygains three tests:by_runscovers every item once, in eight runs for two threads, the first on thecalling thread;
collector frees, made on the workers, are all finalized on the calling thread. The
same pool without the pause finalized 1,337 of the 1,600 on worker threads.
The mixed-model, Kenward-Roger, dream, design-check and variance-partitioning tests pass
unchanged (191 tests). The full suite on top of the pull requests before it: 4,926
passed, 18 skipped, also with bounds checks and with the rounding changed; Python 3.14:
4,947 passed, without the warning of the Tk objects; Python 3.15: 4,841 passed, 107
skipped.