Skip to content

Add memory benchmarking and arena trimming to reduce waste#254

Merged
bartveneman merged 8 commits into
mainfrom
claude/gallant-curie-yf7y5g
Jun 21, 2026
Merged

Add memory benchmarking and arena trimming to reduce waste#254
bartveneman merged 8 commits into
mainfrom
claude/gallant-curie-yf7y5g

Conversation

@bartveneman

Copy link
Copy Markdown
Member

Summary

This PR adds comprehensive memory diagnostics and optimizes arena memory usage by implementing a trim operation that releases unused pre-allocated capacity after parsing completes.

Key Changes

  • Memory Benchmarking Tool (benchmark/memory.ts): New diagnostic script that measures and compares memory usage across Wallace, css-tree, and PostCSS parsers. Supports baseline comparison to detect regressions with configurable thresholds.

  • Arena Trimming (src/arena.ts):

    • Added trim() method to shrink the arena buffer to exactly match the number of live nodes, releasing wasted pre-allocated capacity
    • Integrated trim call into parse() to automatically optimize memory after parsing completes
    • Adjusted NODES_PER_KB from 325 to 210 based on real-world measurements across Bootstrap, Tailwind, and custom CSS files
    • Reduced CAPACITY_BUFFER from 1.2 to 1.1 for tighter pre-allocation (effective ceiling: 231 nodes/KB)
  • Test Updates (src/arena.test.ts):

    • Added comprehensive tests for the new trim() method covering normal operation, data preservation, and edge cases
    • Updated real-world CSS framework tests to verify zero growth and tight capacity after parsing
  • Build Configuration: Added benchmark:memory npm script and updated .gitignore to exclude benchmark results

Implementation Details

The trim operation safely shrinks the ArrayBuffer by copying live data to a new, smaller buffer. It's a no-op when capacity already equals count, making it safe to call multiple times. The memory benchmarking tool uses Node's --expose-gc flag to force garbage collection between measurements, ensuring accurate heap snapshots and enabling regression detection against saved baselines.

https://claude.ai/code/session_01TADcY37pEm5JGh8YJ7UuVQ

claude added 3 commits June 21, 2026 10:18
…ession detection

Measures parse-only and parse+walk memory across Wallace, csstree, and postcss,
broken down into heap vs external (ArrayBuffer) so arena overhead is visible separately.
Also reports arena stats (node count, capacity, waste%, growths) to diagnose over-allocation.

Run: node --expose-gc benchmark/memory.ts
Save baseline: pnpm run benchmark:memory:baseline
Compare branch vs baseline: pnpm run benchmark:memory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TADcY37pEm5JGh8YJ7UuVQ
Adds CSSDataArena.trim() which shrinks the backing ArrayBuffer to
count * BYTES_PER_NODE, eliminating the 50-65% of wasted pre-allocated
capacity that was visible as external memory on the parse result.

Before: Bootstrap parse = 3336 KB external (106k-node pre-alloc for 37k actual)
After:  Bootstrap parse = 1174 KB external (37k * 32 bytes, zero waste)

The NODES_PER_KB heuristic is still useful for avoiding arena growths
during parsing; it just no longer determines the final allocation size.

Also fixes the memory benchmark's measurement loop to hold the parse
result in a module-level sink across the post-operation GC. A local
`void result` is insufficient because V8's JIT can shorten the
variable's lifetime before the GC call runs, causing the arena buffer
to be collected and the delta to read near-zero.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TADcY37pEm5JGh8YJ7UuVQ
Measured actual nodes/KB across real-world files:
  bootstrap 137 | bootstrap.min 166 | tailwind 157 | tailwind.min 195 | small 198

210 * 1.1 = 231 nodes/KB effective ceiling, ~16% above the observed max of 198.
All tested files still parse with zero arena growths.

Effect on peak arena allocation during parsing (before trim):
  Bootstrap: 3336 KB → 1976 KB  (-41%)
  Tailwind:  43350 KB → 25677 KB (-41%)

Retained memory after trim is unchanged (always exact node_count * 32 bytes).
The constants now only govern transient peak during the parse call itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TADcY37pEm5JGh8YJ7UuVQ
@codecov-commenter

codecov-commenter commented Jun 21, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 1.06kB (0.56%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
@projectwallace/css-parser-esm 191.94kB 1.06kB (0.56%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: @projectwallace/css-parser-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
node-types-CdY1J4fH.d.ts (New) 25.17kB 25.17kB 100.0% 🚀
css-node-viJ1-w0x.js (New) 14.88kB 14.88kB 100.0% 🚀
parse.js 21 bytes 9.98kB 0.21%
arena-DM2e0fgL.js (New) 7.87kB 7.87kB 100.0% 🚀
arena-Rkw9tCbD.d.ts (New) 4.75kB 4.75kB 100.0% 🚀
node-types-mntWKkN-.d.ts (Deleted) -25.17kB 0 bytes -100.0% 🗑️
css-node-BTgoFWDU.js (Deleted) -14.88kB 0 bytes -100.0% 🗑️
arena-DnV0a_Ne.js (Deleted) -7.2kB 0 bytes -100.0% 🗑️
arena-CSK5NfHq.d.ts (Deleted) -4.38kB 0 bytes -100.0% 🗑️

Files in parse.js:

  • ./src/parse.ts → Total Size: 9.54kB

Files in arena-DM2e0fgL.js:

  • ./src/arena.ts → Total Size: 7.08kB

@codecov-commenter

codecov-commenter commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.22%. Comparing base (7a8048d) to head (c0f2d75).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #254      +/-   ##
==========================================
+ Coverage   93.20%   93.22%   +0.01%     
==========================================
  Files          17       17              
  Lines        3062     3071       +9     
  Branches      853      854       +1     
==========================================
+ Hits         2854     2863       +9     
  Misses        208      208              

☔ View full report in Codecov by Harness.
📢 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.

claude added 5 commits June 21, 2026 15:19
Fixes oxlint "Do not construct dirname" error in benchmark/memory.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@bartveneman bartveneman merged commit 25de2c7 into main Jun 21, 2026
4 of 5 checks passed
@bartveneman bartveneman deleted the claude/gallant-curie-yf7y5g branch June 21, 2026 18:18
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.

3 participants