Native record representation + inline nil?/some? (#222)
Records were a jrec holding an alist of (kw . val) conses: ~113B/node, built fresh per construction, field reads a list scan. Replace that with a shared per-type descriptor (tag + field keywords + an eq?-keyed keyword->index table) plus a flat per-instance value vector and an extension map for any non-field keys assoc'd on (jolt-nil when there are none). Construction now allocates one vector instead of a cons chain and a field read is an index lookup. binary-trees construction allocation drops 2.085GB -> 1.19GB. That alone barely moved binary-trees wall-time: profiling showed the read loop, not allocation, dominates, and the read loop's own allocation came from (nil? l) lowering to (jolt-invoke (var-deref "clojure.core" "nil?") l), which conses its args every call. Add nil?/some? to the backend native-op table so they inline to jolt-nil?/jolt-some? (and drop the truthy wrapper, like the other predicates). check-tree's read loop goes from 1.476GB allocated to zero; binary-trees 18.9x -> 9.7x vs JVM. The remaining gap is the field-read dispatch chain (jolt-c3mw). Two JVM divergences fixed along the way, both certified: - dissoc of a declared field downgrades a record to a plain map (was kept as a record); an extension key still drops cleanly. - map->R keeps extension keys (was dropping anything outside the declared basis). 16 new corpus rows pin assoc/dissoc/count/keys/seq/=/hash/extension-field behavior against JVM Clojure. Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
eacfa04e5b
commit
8bea1abe12
8 changed files with 1049 additions and 934 deletions
|
|
@ -598,7 +598,12 @@
|
|||
;; deftype already defines ->name (= the ctor); no (name. …) interop needed,
|
||||
;; so defrecord compiles too. map->name builds via that ctor.
|
||||
(deftype ~name-sym ~fields)
|
||||
(def ~mapf (fn* [~m] (~arrow ~@(map (fn [f] `(get ~m ~(keyword (name f)))) fields))))
|
||||
;; build via the positional ctor for declared fields, then carry any
|
||||
;; remaining keys as extension fields (JVM keeps them on the record).
|
||||
(def ~mapf (fn* [~m]
|
||||
(reduce-kv assoc
|
||||
(~arrow ~@(map (fn [f] `(get ~m ~(keyword (name f)))) fields))
|
||||
(dissoc ~m ~@(map (fn [f] (keyword (name f))) fields)))))
|
||||
~@(mapcat (fn [g]
|
||||
(let [proto (first g)
|
||||
names (distinct (map (fn [spec] (name (first spec))) (rest g)))]
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
"range" "jolt-range" "take" "jolt-take" "drop" "jolt-drop"
|
||||
"keys" "jolt-keys" "vals" "jolt-vals"
|
||||
"even?" "jolt-even?" "odd?" "jolt-odd?" "pos?" "jolt-pos?" "neg?" "jolt-neg?"
|
||||
"zero?" "jolt-zero?" "identity" "jolt-identity"
|
||||
"zero?" "jolt-zero?" "identity" "jolt-identity" "nil?" "jolt-nil?" "some?" "jolt-some?"
|
||||
"ex-info" "jolt-ex-info"})
|
||||
|
||||
;; Value-position resolution for a clojure.core ref passed AS A VALUE (to map /
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
"first" #(= % 1) "rest" #(= % 1) "next" #(= % 1) "seq" #(= % 1)
|
||||
"reverse" #(= % 1) "last" #(= % 1) "keys" #(= % 1) "vals" #(= % 1)
|
||||
"even?" #(= % 1) "odd?" #(= % 1) "pos?" #(= % 1) "neg?" #(= % 1)
|
||||
"zero?" #(= % 1) "identity" #(= % 1)
|
||||
"zero?" #(= % 1) "identity" #(= % 1) "nil?" #(= % 1) "some?" #(= % 1)
|
||||
"cons" #(= % 2) "filter" #(= % 2) "remove" #(= % 2) "into" #(= % 2)
|
||||
"take" #(= % 2) "drop" #(= % 2) "map" #(>= % 2) "apply" #(>= % 2)
|
||||
"reduce" #(or (= % 2) (= % 3)) "range" #(and (>= % 0) (<= % 3))
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
(def ^:private bool-returning-ops
|
||||
#{"<" "<=" ">" ">=" "jolt=" "jolt-not"
|
||||
"jolt-even?" "jolt-odd?" "jolt-pos?" "jolt-neg?"
|
||||
"jolt-zero?" "jolt-empty?" "jolt-contains?"})
|
||||
"jolt-zero?" "jolt-empty?" "jolt-contains?" "jolt-nil?" "jolt-some?"})
|
||||
|
||||
;; Numeric-specialized op strings. jolt.passes.numeric tags an arithmetic invoke
|
||||
;; :num-kind :double|:long when every operand is that kind; these are the Chez
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue