Repository navigation
Share record, enumeration, and array models within a process - #240
Open
cbrisebois-lasp wants to merge 2 commits into
Open
cbrisebois-lasp wants to merge 2 commits into
cbrisebois-lasp wants to merge 2 commits into
Conversation
cbrisebois-lasp
commented
Oct 5, 2026
cbrisebois-lasp
force-pushed
the
perf/share-type-models
branch
2 times, most recently
from
October 5, 2026 19:40
9603bb9 to
fe6e1eb
Compare
cbrisebois-lasp
marked this pull request as ready for review
October 5, 2026 19:48
dinkelk
reviewed
Oct 7, 2026
|
|
||
| # Shareable models already loaded by this process, by file and load | ||
| # arguments. A model class opts in with its shareable attribute; see base. | ||
| _shared_models = {} |
Collaborator
There was a problem hiding this comment.
Seems like an in-memory cache should live with the model cache database so it is transparent. I tried a similar change a few years ago, but rejected it because it did not affect performance much. But if it is more memory efficient, maybe we should reconsider.
I really don't like the "sharable" flag here. Ideally we could remove need for something like that. The tracking overhead on all model types seems brittle and error prone to manage.
cbrisebois-lasp
force-pushed
the
perf/share-type-models
branch
from
October 8, 2026 00:28
fe6e1eb to
71dc99d
Compare
cbrisebois-lasp
force-pushed
the
perf/share-type-models
branch
2 times, most recently
from
October 26, 2026 18:28
7836b9c to
2619811
Compare
Pregeneration ran one pool worker per core, and a worker generating from a large model, such as a flight assembly with every component loaded, reaches several gigabytes, so a clean build of a big project asked for many times the memory of the machine. The kernel then killed workers, and because a multiprocessing pool waits forever for a result a killed worker never returns, the build sat idle with the memory still held rather than failing. Each target now runs in its own short-lived forked process, admitted only while the memory it is expected to need is free after accounting for the growth still ahead of the running workers. The expected need is kept per kind of target, the input model's type, since an assembly-level generator costs orders of magnitude more than a record's: it starts as an estimate with one worker of the kind run at a time as a probe, becomes the peak the finished workers reached, is raised by any running worker that outgrows it, and persists across the batches of one build step. A killed worker is reported as a failure whose target falls back to the ordinary redo path.
Every reference to a type loaded its own copy of the model: a component built a fresh Packed_U16 for each field that used it, and that copy loaded fresh copies of its own field types, so one load of a large assembly made some 2,400 model loads for fewer than 500 distinct files and held a tree of duplicates where a shared graph would do. The types are never altered after they load, unlike a component, which the assembly fills in with its instance data, so a model class now marks itself shareable and the loader hands back the one object it already built for that file and those arguments. Sharing applies on the load path; a component restored from the on-disk model cache still carries its own embedded copies of its types, so a warm build keeps those duplicates. Measured on a flight assembly, a cold load drops from 101 to 59 seconds and its retained memory from 267 to 62 MiB, with the generated sources byte for byte unchanged.
cbrisebois-lasp
force-pushed
the
perf/share-type-models
branch
from
November 2, 2026 18:40
2619811 to
7516b48
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.
Problem
Every reference to a type loads its own copy of the model. A component
builds a fresh
Packed_U16for each field that uses it, and that copyloads fresh copies of its own field types. One cold load of the EMA flight
assembly makes 2,399 model loads for 475 distinct files (
packed_u16alone 205 times), holds a tree of duplicates where a shared graph would
do, and writes 1.8 GB to the model cache on the way. That load is what
every assembly-level generator pays, so it sets the cost of a clean build.
Change
Record, enumeration, and array models are marked
shareable, andmodel_loader.load_modelreturns the one object it already built for ashareable file (and load arguments) within the process. These types are
only completed after they load (a record fills in its fields' type ranges
on first request), never altered. Components stay unshared because the
assembly writes instance data into each one it loads, and submodels
because their parent writes itself into them. A load that passes
ignore_cache=Truestill builds a fresh object, which then becomes theshared one, so the flag keeps its meaning.
Measured on one cold flight assembly load
Verification
identical to those generated without sharing, apart from the generation
timestamp line.
redo/test/shared_models: a record, enumeration, and array are shared;ignore_cachebuilds fresh and replaces; a component is built anew onevery load.
gen/test/recordssuites pass; flake8 clean.Limitation
Sharing applies on the load path. A component restored from the on-disk
model cache is unpickled with its own embedded copies of its types, so a
warm build keeps those duplicates. Fixing that means pickling with
persistent ids and is a separate change.