From 707737d989128243a282fff834b70aa6255d0800 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Fri, 26 Jun 2026 00:52:34 -0400 Subject: [PATCH] Fast record field reads: single eq? scan, skip the get-arm walk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (:field rec) / (get rec :field) lowers to (jolt-get rec kw), which walked the get-arm list to reach the jrec arm, then did jrec-has? + jrec-lookup — TWO linear scans, each comparing keys through the generic jolt=2 equality dispatcher. Field keys are interned keywords, so: - jrec-key=? compares a keyword query by eq? (jolt=2 only for non-keyword keys), - jrec-ref does ONE scan (vs has?+lookup) and runs a deftype's ILookup valAt only when the field is genuinely absent (present-nil still returns nil, not default), - jolt-get-dispatch checks jrec? first, skipping the get-arm walk for the hottest get target. jrec-lookup/jrec-has? (used by =, contains?, etc.) get the fast compare too. binary-trees 135x->18.9x, dispatch 121x->26.4x, mono-dispatch 327x->108x vs JVM. Runtime .ss (collections.ss + records.ss), no re-mint; make test + shakesmoke + buildsmoke green, record get/assoc/keys/=/count semantics unchanged. --- host/chez/collections.ss | 13 +++++++++---- host/chez/records.ss | 32 +++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/host/chez/collections.ss b/host/chez/collections.ss index 108bc6a..35936a5 100644 --- a/host/chez/collections.ss +++ b/host/chez/collections.ss @@ -239,11 +239,16 @@ ((string? coll) (let ((i (->idx k))) (if (and (fixnum? i) (fx>=? i 0) (fx (lambda (m) (jolt-invoke m coll k d))) + (else d))) + ((jrec-key=? (caar ps) k) (cdar ps)) + (else (loop (cdr ps))))))) ;; mutate a deftype's mutable field in place: the pairs are runtime cons cells, ;; so set-cdr! updates the field. (set! field v) inside a method ;; lowers to this; returns v, as set! does. @@ -77,12 +93,10 @@ ;; compiled to (get inst :field), never recurse); a NON-field key on a deftype that ;; implements clojure.lang.ILookup routes to its valAt (core.match's pattern types ;; compute ::tag in valAt), else the default. -(register-get-arm! jrec? - (lambda (coll k d) - (cond ((jrec-has? coll k) (jrec-lookup coll k d)) - ((find-method-any-protocol (jrec-tag coll) "valAt") - => (lambda (m) (jolt-invoke m coll k d))) - (else d)))) +;; jrec is the hottest get target (every record field read); jolt-get-dispatch +;; (collections.ss) checks jrec? directly and calls jrec-ref, skipping the get-arm +;; walk. This registration is the equivalent fallback for any other caller. +(register-get-arm! jrec? jrec-ref) ;; A jrec is a defrecord (map of fields) by default, BUT a deftype that ;; implements a clojure.lang collection interface carries the op as an inline ;; method — prefer that method, else fall back to the field/map behavior. (jrec-cl