Skip to content

Bound parallel checker peak memory to O(schedulers), not O(batch)#15544

Closed
aspett wants to merge 1 commit into
elixir-lang:mainfrom
aspett:ap/parallel-checker-memory
Closed

Bound parallel checker peak memory to O(schedulers), not O(batch)#15544
aspett wants to merge 1 commit into
elixir-lang:mainfrom
aspett:ap/parallel-checker-memory

Conversation

@aspett

@aspett aspett commented Jul 6, 2026

Copy link
Copy Markdown

In a large application (30k+ modules), we see peak RAM consumption in some compilation scenarios reach near 10Gi or more. Upon investigation, the cause appears to be the ParallelChecker is expanding and caching the AST of every module at once before checking begins.

By changing this behaviour to defer expansion until checking, we can reduce peak memory use by 40-80%. (Measured 8.46Gi -> 1.12Gi on last attempt)

The tradeoff is compile time though; deferring on a small recompile on this codebase added ~9%

The group verification phase held every module's expanded definitions AST
resident at once: handle_call(:start) caches all modules before checking any,
and each checker worker retained its module_tuple from :cache until :check.
On a large batch this is the dominant memory term (~0.85 MB/module; verifying
5000 modules held ~3.5 GB of process memory).

Three changes keep peak proportional to the scheduler count:

* Defer reconstructing the definitions AST from :cache to :check, holding
  only the beam location and re-reading lazily, so only the ~schedulers
  modules being checked concurrently materialize their tuple. Guarded on
  debug_info being kept (the lazy re-read needs it); otherwise keep the
  in-memory tuple so checking still runs.
* Garbage-collect each worker after caching, before it blocks on :check, to
  reclaim the transient beam read/parse heap it would otherwise hold for the
  whole batch.
* Cache in bounded waves in handle_call(:start) instead of starting every
  worker at once, while still caching all modules before any checking begins.

Warnings and emitted .beam files are unchanged. On a full compile of a
5000-module project: peak RSS ~1934 MB -> ~844 MB (process memory 1156 -> 96 MB).
@josevalim

Copy link
Copy Markdown
Member

PR #15545 with more conservative changes and a few approaches for us to try!

@aspett aspett closed this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants