Repository navigation
Description
Every minor collection scans all registered native weak references, including old references to live old targets. Large weak-reference caches therefore add recurring nursery-collection latency even when their target population is unchanged and cannot be reclaimed by that collection.
Measured on Windows x64, MSVC 19.44.35213, Haxe 4.3.7 and libhxcpp fe67557, release HXCPP_M64 with HXCPP_GC_GENERATIONAL:
| Retained weak-reference wrappers | Empty-reference control, median minor GC | References to retained old targets, median minor GC |
|---|---|---|
| 100,000 | 0.1263 ms | 0.2899 ms |
| 1,000,000 | 0.7123 ms | 3.3421 ms |
| 2,000,000 | 1.4532 ms | 6.8315 ms |
Each case runs in a fresh process. Both controls allocate and retain the same number of Item targets, Haxe WeakRef wrappers and native weak-reference objects. The difference is whether the native weak references point to those retained targets or to null. The null-reference objects do not enter the global processing list. The targets remain strongly reachable in both cases, so none of the registered references should clear.
Setup creates temporary garbage on a joined native worker to leave sufficient free heap for nursery mode. GC stays enabled. Each process performs three setup collections, then 71 timed Gc.run(false) calls, discarding ten warm-up samples and reporting the median of 61. hx::gByteMarkID is unchanged across every timed call, confirming minor collections; all cases recorded zero full collections. Target/ref value checks happen outside the timer. These are focused GC measurements, not an application-wide speedup estimate.
In src/hx/gc/Immix.cpp, WeakRef registers each non-null target in sWeakRefs. RunFinalizers walks the entire list after marking, checking each reference's mark byte and calling IsWeakRefValid on its target. This processing runs after both minor and full collection. The native WeakRef target does not have a normal mutation API; Haxe WeakRef.set creates a new native weak-reference object and updates its wrapper, rather than replacing the old native object's target in place.
Consider separating nursery weak-reference candidates from old references whose targets are old, so a minor collection need not revisit every stable old entry. Full collection still needs complete liveness processing. Promotion, moving GC, dead reference objects, bound-method target handling and replacement through WeakRef.set all need coverage. Do not make the target strongly owned or delay clearing a dead young target to avoid the scan. Measure the bookkeeping cost for small populations and frequent set/replacement workloads as well as large stable caches.
Conventional local coverage was added first at test/regression/GenerationalWeakRefLifecycle/{Main.hx,build.hxml,stdout.txt}. It passes on the current runtime. It retains old targets, replaces some old wrappers with weak references to young targets, checks held/dead young targets after collection, then releases the old targets and confirms full collection clears them. Construction, checks and releases run on joined workers so stale main-thread stack values do not decide target liveness. The test has no timing assertions.
This concerns the optional generational collector and can be addressed inside libhxcpp without compiler or standard library changes. Related: #302 concerns finalizer registrations, which use different processing tables; #202 concerns callable-adapter weak-reference correctness rather than this scan cost. No runtime implementation change has been made.
Activity
Look at new finalizers and weak references only in a minor collection
Look at new finalizers and weak references only in a minor collection
dimensionscape commentedon Oct 25, 2026
Fixed in
426663cc9851a81ddfe8c6f4e196ac4beb0ac8e1, with #302. Weak references are split the same way: one whose reference and target both survived a collection is left alone by minor collections and checked by every full one. Nothing about clearing changes: a dead young target is cleared by the minor collection that finds it dead. With 1,000,000 weak references to retained targets a median minor collection went from 2.16 ms to 0.29 ms, the same as with null references. Test: GenerationalWeakRefLifecycle. Its last check, that full collections clear released targets, is held on Windows: the Windows CI runners keep one released target alive, on main before this change too, which is #367.