Files
oxc-project__oxc/tasks/track_memory_allocations/README.md
Boshen 2338c13f60 feat(track-memory-allocations): track heap deallocs, alloc bytes, and peak growth (#24619)
Adds three heap-side metrics to the allocation snapshots, using the metric machinery from #24587:

- `sys deallocs` (exact count) — system deallocations per stage; read against `sys allocs` it shows whether a stage churns or retains heap.
- `sys alloc bytes` — cumulative bytes requested from the system allocator per stage (allocation sizes plus reallocation growth).
- `sys peak growth` — high-water mark of live heap during a stage relative to the live bytes at stage start, via live-bytes accounting in the `GlobalAlloc` wrapper. Captures transient heap a stage needs even when it frees it before finishing.

Arena chunk operations are excluded from all `sys` metrics at the source: `oxc_allocator` marks each chunk allocation/deallocation immediately before calling the system allocator (task-only `track_allocations` feature, compiled out everywhere else), and the task's tracking allocator consumes the marker and skips the call. This replaces the previous global chunk counter + per-stage subtraction: a subtraction can correct counts but not a high-water mark, and chunk sizes are quantized and platform-dependent (whether an arena needs one more chunk depends on byte totals that vary across architectures with type layout — the #22621 mechanism). The first CI run demonstrated this concretely: kitchen-sink's minifier peak differed deterministically by 720 kB between aarch64 and x86_64 because the mangler's temporary arena crossed a chunk-growth boundary only on x86_64; and antd's formatter "peak" was 762 MB of main-arena chunk-doubling history rather than formatter behavior (now 13.8 MB of actual heap use).

Cross-platform tolerances: the cumulative byte metrics still drift with the number of heap hashbrown tables allocated (their control-group width is architecture-dependent), so they use a relative tolerance (1%, 4 kB floor) via the committed-value comparison from #24565; `arena size` keeps its tight 1024-byte tolerance.

Sample values for checker.ts (2.9 MB source): parser needs only 2.4 kB of transient heap (fully arena-based), semantic peaks at 3.9 MB, the minifier is the pipeline's largest heap consumer at 10.2 MB, and the formatter requests 5.9 MB total while peaking at 3 MB (heavy alloc/free churn).

🤖 Implemented with [Claude Code](https://claude.com/claude-code).
2026-07-17 12:51:14 +00:00

2.3 KiB

Oxc allocations stats

This task keeps track of the number of system allocations as well as arena allocations and the total number of bytes allocated. This is used for monitoring possible regressions in allocations and improvements in memory usage.

Update the snapshots with cargo allocs.

Metrics

Recorded per file and per stage (parser, semantic, transformer, minifier, formatter):

  • file size - size of the source text
  • sys allocs / sys reallocs / sys deallocs - system allocator activity during the stage
  • sys alloc bytes - total bytes requested from the system allocator during the stage (allocation sizes plus reallocation growth)
  • sys peak growth - high-water mark of live heap memory during the stage, relative to the live bytes when the stage started; captures transient heap the stage needs even when it frees it again before finishing
  • arena allocs / arena reallocs - arena allocator activity during the stage
  • arena size - bytes used in the arena at the end of the stage (cumulative: the arena is not reset between stages, so this includes the AST built by the parser)

Arena chunk allocations and deallocations are excluded from all sys metrics: chunk sizes are quantized and platform-dependent (whether an arena needs one more chunk depends on byte totals that vary across architectures), and for long-lived arenas chunk growth reflects the arena's history rather than the stage's own behavior. Arenas mark their chunk operations for the tracking allocator to skip.

Cross-platform reproducibility

Allocation counts are identical on all platforms: the task pins mimalloc as the global allocator and excludes arena chunk allocations from the system allocation counts. arena size is not exactly reproducible: type layout differs slightly across architectures (e.g. hashbrown's table control groups are 16 bytes on x86_64 vs 8 bytes on aarch64), which shifts arena byte totals by up to ~128 bytes per file. So that a snapshot generated on any platform passes CI on the others, the snapshots are YAML: cargo allocs parses the committed snapshot and keeps a byte metric's committed value when the measured one is within that metric's tolerance (arena size: 1024 bytes; sys alloc bytes and sys peak growth: 1%, minimum 4 kB); only differences beyond the tolerance rewrite the value.