perftest: twin-handle lifecycle benchmark — plain vs .gc_managed()#83
Conversation
Add a minimal opaque twin pair (Token / TokenGc, identical Rust shape,
one plain, one .gc_managed()) and six Bench.kt rows pricing the GC-
cleanup machinery head-to-head per handle:
handle_close create + explicit close plain 34 ns gc 119 ns
handle_drop create + drop (no close) plain 20 ns* gc 171 ns
handle_call method on a live handle plain 19 ns gc 19 ns
(* plain drop leaks natively — allocation cost only, today's
leak-on-drop behavior; gc includes the Cleaner-driven free)
(medians of 3 runs, N=5M classic rows / 2M handle rows, Apple Silicon)
So the mechanism costs ~85 ns extra on an explicitly closed handle and
~150 ns on a GC-collected one (cell alloc + Cleaner registration + CAS
ticket + PhantomRef churn), and NOTHING on the call path — the atomic-
cell ptr getter matches the plain field, and pre-#82 vs post-#82 medians
of the untouched put/get/callback rows differ only within noise (±3%,
mixed signs), so the NativeHandle.ptr open-property change is free for
plain classes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up: end-to-end zenoh throughput measurement
(main×main control column stayed at ~490k/~430k msg/s across both runs — machine stable.) The observed +245–337 ns/msg matches the microbench prediction (~85 ns close-path + ~151 ns drop-path ≈ 236 ns, plus GC-pressure amplification from the PhantomRef churn at ~700k registrations/s). Conclusion: the per-class opt-out for hot-path per-message handles (KeyExpr, ZBytes) is justified — gc-managing them costs a double-digit throughput percentage; the backstop classes (Session, Publisher, …, Encoding) pay only at create/close time, and nothing on the call path. The temporary flip was reverted; zenoh-flat-jni is unchanged. 🤖 Generated with Claude Code |
What
Measures the real cost of the
.gc_managed()machinery (#82) with a twin minimal handle pair —Token(plain) vsTokenGc(.gc_managed()), identical Rust shape — plus six new perftest rows.Results (medians of 3 runs, N=5M classic / 2M handle rows, Apple Silicon)
handle_close— create + explicit closehandle_drop— create + drop, no closehandle_call— method on a live handle* plain drop leaks natively (allocation cost only — today's leak-on-drop behavior); the gc number includes the Cleaner-driven free and PhantomRef/GC churn.
Existing hot paths are unaffected: pre-#82 (
598f740) vs this branch, medians of the untouchedput/get/callback(+_vec) rows differ only within noise (±3%, mixed signs) — theNativeHandle.ptropen-property change costs nothing for plain classes, and the atomic-cell getter matches the plain field on the call path.Interpretation for the zenoh hot path
A received zenoh sample materializes 2 handles (KeyExpr + payload ZBytes). GC-managing them would add roughly 85–150 ns × 2 ≈ 0.2–0.3 µs per message of allocation/registration/cleanup work against the ~1 µs/msg subscriber floor — i.e. a potential double-digit percentage throughput cost, to be confirmed end-to-end with
run_thr_java.sh(follow-up measurement). The per-class opt-out for KeyExpr/ZBytes in zenoh-flat-jni stays justified; the zero call-path cost confirms the backstop classes (Session, Publisher, …) pay only at create/close time.265 lib tests green; fmt + clippy clean; covertest untouched.
🤖 Generated with Claude Code