Skip to content

memory: charge the refresh's retained parse pool and the no-owner build residue #2706

Description

@ScriptedAlchemy

Summary

Follow-up to #2490 / #2525 after the idle-thread release, parser-stack drop and graph owner heaps. With both graph owners resident, a settled daemon's uncharged anonymous memory now levels off at about 300 MB. Two things are still uncharged and grow with the corpus or the refresh, so a fixed runtime allowance and the #2490 invariant test cannot be added yet.

Evidence

Release production build, isolated HOME/TRACEDECAY_DATA_DIR, systemd-run --user --scope -p MemoryMax=6G -p MemorySwapMax=1G, 96-core host. Gap = anon + swap - charged owners.

1. The refresh increment's retained parse pool and build residue. SharedRetainedParsePool keeps trees, extraction artifacts and sources for every document an increment re-extracted (up to 256 docs / 64 MiB of source). No owner charges them.

corpus cold gap gap after a 40-file refresh commit
every 20th tracked file (293 files) 212,824,510 279,094,351
every 5th tracked file (1,164 files) 266,709,683 318,949,300
synthetic, 300 Rust files x 160 fns 178,809,782 375,914,470

The retained tree bytes have no live measure. tree-sitter allocates through glibc malloc. One way forward: route tree-sitter to mimalloc with ts_set_allocator, build and reparse retained documents inside an OwnerHeapV1 scope, and register the pool as a resident owner charged by its heap pages. Another: stop retaining across builds, measuring the reuse rate first.

2. The no-owner residual. With both graph owners idle-released, what stays resident:

corpus anon + swap, no owners [anon:mimalloc] glibc / unnamed anon
293 files 227,074,048 165.6 MB 51.5 MB
1,164 files 294,797,312 205.3 MB 78.0 MB
full clone (5,855), after a refresh 435,204,096 343.6 MB 76.8 MB

The glibc part levels off (SQLite page caches filling to their per-connection bound). The mimalloc part keeps growing, about 34-45 KB per file. #2661's heaptrack puts non-owner live Rust at about 5 KB per file, so most of this is pages pinned by a few long-lived blocks from index builds and activation. Next step: an allocation-site profile of the no-owner state (heaptrack on a system-allocator build, or an MI_STAT build) to find which long-lived allocations pin build pages. Then either allocate those results after the build scratch is freed, or build them in their own owner heap.

Acceptance

Anon + swap stays within charged owners plus one fixed allowance on a cold index and after a refresh, from 300 to 5,855 files, including a heavy-file corpus. A daemon test then asserts it and fails on today's master.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions