mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
Every release of a LifetimeRegion, and every retry at region creation, ran scr_collect_cycles(): full cycle-collector passes over the thread's heap, to a fixpoint, before checking any region, in case dead cycles of ordinary objects held counted references into it. Freeing a region ran them again whenever the owned ordinary objects left counts behind after the region released its fields. Next to a large heap of candidates (a type checker beside per-file emit regions) those passes cost far more than the regions. A retry now checks the due regions first. A region whose references are all explained frees without a pass; only when one would be kept does the heap get the full pass, after which every due region is judged as before. The first check leaves a region it cannot free as it was, so the backoff, the kept report and SCRIPTC_REGION_DEBUG see only the check after the pass. It keeps what its walk found: a region whose counted references the pass did not change is judged from that, without walking it again. A pass that freed a dead cycle referring only to an owned object leaves the count unchanged; such a region waits for its next attempt. After a region releases its instances' fields, what can still hold counts is the owned ordinary objects' cycles. Each has a member that lost a reference in those releases and is buffered as a candidate, and the release walk entered every cycle member. The cycle collector gains a pass limited to a set of candidates (scr_cyc_collect_set): it takes the set's members out of the buffers up to a given generation, reading only the buffers' pointers, so the candidates it leaves are not touched, and it walks at full depth without moving the schedule's baselines. A dead object such a pass reaches through a candidate it did not take leaves its buffer before it is freed. Freeing a region runs these passes over the walk's objects from the youngest buffer out, and the whole heap only if counts remain. SCRIPTC_REGION_STATS also prints, per thread at exit, how many full passes over the heap regions needed.
Lifetime-region benchmarks
Programs that build generations of ProgramLifetime objects and free each generation with a LifetimeRegion (@scriptc/experimental). Every variant prints the same result line before its timings.
| Program | Shape |
|---|---|
generations.ts |
Per-file syntax trees and binder symbols in one region per file, checker state (types, signatures, node and symbol links in paged tables, literal and union caches) in one region per generation. edit re-parses one file per generation, all re-parses every file. |
compiler.ts |
A tokenizer, recursive-descent parser, binder and checker over generated sources, rebuilt from scratch in one region per build, like a watch mode that does not reuse syntax trees. |
emit.ts |
A large live graph of ordinary objects with parent links, standing in for a type checker, and a per-file emit that builds its output in a region released right after. |
$ node benchmarks/regions/run.mjs --baseline=../scriptc-main --runs=7
Each configuration is timed in five variants, interleaved: on (ProgramLifetime classes, no regions), off (the same classes without implements ProgramLifetime), region, region-plain (the region build run without constructing a region, which measures what supporting regions costs a one-shot run) and Node.js. --baseline builds on and off with another checkout.