Refactor phase 5c: dispatch core collection ops on :jolt/type via one case (jolt-bvek)
core-count/core-seq/core-conj each walked a chain of (and (table? x) (= :jolt/X
(get x :jolt/type))) predicates — re-fetching the type tag per predicate and, for
conj, a 14-deep nested if. Replace with a single (case (get coll :jolt/type) ...)
per op: the type is read once and the arm calls the concrete op directly. Host
values (tuple/array/nil) and tuple-based shape-recs carry no :jolt/type and stay
in a per-op fallback cond (shape-rec? kept before tuple? — shape-recs ARE tuples).
Factor the two map-conj paths (phm vs struct) into one conj-into-map parameterized
by the assoc fn.
Behavior-preserving: the :jolt/type tags are disjoint, so the case is an exact
re-expression of the predicate chain; the fallbacks reproduce the originals
(incl. count erroring on a raw host table, which only seq ever handled).
This is the perf-neutral realization of the planned collection vtable. A shared
:jolt/type->{ops} table was tried first and REGRESSED core-bench ~2-4% (table
lookup + indirect call on the hot path); the inline case instead runs ~1.7%
FASTER than main (4864ms vs ~4950ms baseline) since one type-get + a jump beats
the sequential predicate chain. Full gate green (suite >=4695/88, fixpoint).
This commit is contained in:
parent
dd64fd4e74
commit
dd4100db73
1 changed files with 97 additions and 99 deletions
|
|
@ -36,70 +36,59 @@
|
||||||
[coll op]
|
[coll op]
|
||||||
(get (coll :ops) op))
|
(get (coll :ops) op))
|
||||||
|
|
||||||
|
# Merge conj items onto a map receiver. assoc1 is phm-assoc for a phm receiver,
|
||||||
|
# map-assoc1 for a struct/host-table receiver — the only thing that differs
|
||||||
|
# between the two map-conj paths.
|
||||||
|
(defn- conj-into-map [coll xs assoc1]
|
||||||
|
(var result coll)
|
||||||
|
(each x xs
|
||||||
|
(cond
|
||||||
|
# conj nil onto a map is a no-op (Clojure)
|
||||||
|
(nil? x) nil
|
||||||
|
# conj a map -> merge its entries
|
||||||
|
(map-value? x)
|
||||||
|
(each e (map-entries-of x) (set result (assoc1 result (in e 0) (in e 1))))
|
||||||
|
# a [k v] entry: exactly a 2-element vector (Clojure throws otherwise — and
|
||||||
|
# merge inherits this strictness through conj)
|
||||||
|
(and (or (pvec? x) (tuple? x) (array? x)) (= 2 (vcount x)))
|
||||||
|
(set result (assoc1 result (vnth x 0) (vnth x 1)))
|
||||||
|
(error "Vector arg to map conj must be a pair")))
|
||||||
|
result)
|
||||||
|
|
||||||
|
# Dispatch is on :jolt/type via one case (the type is fetched once and the arm
|
||||||
|
# calls the concrete op directly) rather than a chain of (and (table? x) (= ..))
|
||||||
|
# predicates — same hot-path cost as the predicate chain for the common types,
|
||||||
|
# one place per op. Host values (tuple/array/nil) and tuple-based shape-recs
|
||||||
|
# carry no :jolt/type and stay in the per-op fallback.
|
||||||
(defn core-conj [& args]
|
(defn core-conj [& args]
|
||||||
(if (= 0 (length args)) (make-vec @[]) # (conj) -> []
|
(if (= 0 (length args)) (make-vec @[]) # (conj) -> []
|
||||||
(let [coll (first args) xs (tuple/slice args 1)]
|
(let [coll (first args) xs (tuple/slice args 1)]
|
||||||
(if (nil? coll)
|
(if (table? coll)
|
||||||
# conj onto nil builds a list (prepends): (conj nil 1 2) -> (2 1)
|
(case (get coll :jolt/type)
|
||||||
(do (var result nil) (each x xs (set result (pl-cons x result))) result)
|
:jolt/pvec (do (var r coll) (each x xs (set r (pv-conj r x))) r)
|
||||||
(if (core-sorted? coll)
|
:jolt/phm (conj-into-map coll xs phm-assoc)
|
||||||
((sorted-op coll :conj) coll xs)
|
|
||||||
(if (pvec? coll)
|
|
||||||
(do (var result coll) (each x xs (set result (pv-conj result x))) result)
|
|
||||||
(if (plist? coll)
|
|
||||||
# list: prepend, O(1) per element via structural sharing
|
# list: prepend, O(1) per element via structural sharing
|
||||||
(do (var result coll) (each x xs (set result (pl-cons x result))) result)
|
:jolt/plist (do (var r coll) (each x xs (set r (pl-cons x r))) r)
|
||||||
(if (tuple? coll)
|
# conj onto a seq prepends (Clojure: a Cons cell)
|
||||||
(tuple/slice (tuple ;(array/concat (array/slice coll) xs)))
|
:jolt/lazy-seq (do (var r coll) (each x xs (set r (pl-cons x (realize-for-iteration r)))) r)
|
||||||
(if (array? coll)
|
:jolt/set (apply phs-conj coll xs)
|
||||||
|
:jolt/sorted-map ((sorted-op coll :conj) coll xs)
|
||||||
|
:jolt/sorted-set ((sorted-op coll :conj) coll xs)
|
||||||
|
# other tables (raw host table / deftype instance) conj like a map
|
||||||
|
(conj-into-map coll xs map-assoc1))
|
||||||
|
(cond
|
||||||
|
# conj onto nil builds a list (prepends): (conj nil 1 2) -> (2 1)
|
||||||
|
(nil? coll) (do (var r nil) (each x xs (set r (pl-cons x r))) r)
|
||||||
|
(tuple? coll) (tuple/slice (tuple ;(array/concat (array/slice coll) xs)))
|
||||||
|
(array? coll)
|
||||||
(if mutable?
|
(if mutable?
|
||||||
# mutable mode: arrays are vectors — append in place
|
# mutable mode: arrays are vectors — append in place
|
||||||
(do (each x xs (array/push coll x)) coll)
|
(do (each x xs (array/push coll x)) coll)
|
||||||
# immutable mode: arrays are lists — prepend onto a persistent cons node,
|
# immutable mode: arrays are lists — prepend onto a persistent cons
|
||||||
# sharing the original array as the tail (O(1) per element, no copy)
|
# node, sharing the original array as the tail (O(1) per element)
|
||||||
(do (var result coll) (each x xs (set result (pl-cons x result))) result))
|
(do (var r coll) (each x xs (set r (pl-cons x r))) r))
|
||||||
(if (set? coll)
|
# struct map literal: merge entries
|
||||||
(apply phs-conj coll xs)
|
(conj-into-map coll xs map-assoc1))))))
|
||||||
# conj onto a seq prepends (Clojure: a Cons cell). Without this branch a
|
|
||||||
# lazy-seq fell into the MAP fallback below — clojure.data/diff relies on
|
|
||||||
# (conj seq x) via set/union over (keys m), which is now a lazy seq.
|
|
||||||
(if (lazy-seq? coll)
|
|
||||||
(do (var result coll)
|
|
||||||
(each x xs (set result (pl-cons x (realize-for-iteration result))))
|
|
||||||
result)
|
|
||||||
(if (phm? coll)
|
|
||||||
(do
|
|
||||||
(var result coll)
|
|
||||||
(each x xs
|
|
||||||
(cond
|
|
||||||
# conj nil onto a map is a no-op (Clojure)
|
|
||||||
(nil? x) nil
|
|
||||||
(map-value? x)
|
|
||||||
# conj a map -> merge its entries
|
|
||||||
(each e (map-entries-of x)
|
|
||||||
(set result (phm-assoc result (in e 0) (in e 1))))
|
|
||||||
# a [k v] entry: exactly a 2-element vector (Clojure throws
|
|
||||||
# otherwise — and merge inherits this strictness through conj)
|
|
||||||
(and (or (pvec? x) (tuple? x) (array? x)) (= 2 (vcount x)))
|
|
||||||
(set result (phm-assoc result (vnth x 0) (vnth x 1)))
|
|
||||||
(error "Vector arg to map conj must be a pair")))
|
|
||||||
result)
|
|
||||||
(do
|
|
||||||
(var result coll)
|
|
||||||
(each x xs
|
|
||||||
(cond
|
|
||||||
# conj nil onto a map is a no-op (Clojure)
|
|
||||||
(nil? x) nil
|
|
||||||
(map-value? x)
|
|
||||||
# conj a map -> merge its entries
|
|
||||||
(each e (map-entries-of x)
|
|
||||||
(set result (map-assoc1 result (in e 0) (in e 1))))
|
|
||||||
# a [k v] entry: exactly a 2-element vector (Clojure throws
|
|
||||||
# otherwise — and merge inherits this strictness through conj)
|
|
||||||
(and (or (pvec? x) (tuple? x) (array? x)) (= 2 (vcount x)))
|
|
||||||
(set result (map-assoc1 result (vnth x 0) (vnth x 1)))
|
|
||||||
(error "Vector arg to map conj must be a pair")))
|
|
||||||
result)))))))))))))
|
|
||||||
|
|
||||||
(defn core-assoc [m & kvs]
|
(defn core-assoc [m & kvs]
|
||||||
(when (odd? (length kvs))
|
(when (odd? (length kvs))
|
||||||
|
|
@ -280,20 +269,26 @@
|
||||||
# the collection fns (conj/assoc/get/contains?/…) can branch on them.
|
# the collection fns (conj/assoc/get/contains?/…) can branch on them.
|
||||||
|
|
||||||
(defn core-count [coll]
|
(defn core-count [coll]
|
||||||
|
(if (table? coll)
|
||||||
|
(case (get coll :jolt/type)
|
||||||
|
:jolt/pvec (pv-count coll)
|
||||||
|
:jolt/phm (coll :cnt)
|
||||||
|
:jolt/plist (pl-count coll)
|
||||||
|
:jolt/set (coll :cnt)
|
||||||
|
:jolt/lazy-seq (ls-count coll)
|
||||||
|
:jolt/sorted-map ((sorted-op coll :count) coll)
|
||||||
|
:jolt/sorted-set ((sorted-op coll :count) coll)
|
||||||
|
:jolt/transient (length (if (= :vector (coll :kind)) (coll :arr) (coll :tbl)))
|
||||||
|
# other tables: a deftype record instance counts its fields; a raw host
|
||||||
|
# table is unsupported (matches the original — seq handles it, count never did)
|
||||||
|
(if (get coll :jolt/deftype) (- (length (keys coll)) 1)
|
||||||
|
(error (string "count not supported on " (type coll)))))
|
||||||
(cond
|
(cond
|
||||||
(nil? coll) 0
|
(nil? coll) 0
|
||||||
(shape-rec? coll) (shape-count coll)
|
(shape-rec? coll) (shape-count coll) # shape-recs are tuples, not tables
|
||||||
(core-transient? coll) (length (if (= :vector (coll :kind)) (coll :arr) (coll :tbl)))
|
|
||||||
(core-sorted? coll) ((sorted-op coll :count) coll)
|
|
||||||
(lazy-seq? coll) (ls-count coll)
|
|
||||||
(pvec? coll) (pv-count coll)
|
|
||||||
(plist? coll) (pl-count coll)
|
|
||||||
(set? coll) (coll :cnt)
|
|
||||||
(phm? coll) (coll :cnt)
|
|
||||||
(and (table? coll) (get coll :jolt/deftype)) (- (length (keys coll)) 1)
|
|
||||||
(or (string? coll) (buffer? coll) (struct? coll) (tuple? coll) (array? coll)) (length coll)
|
(or (string? coll) (buffer? coll) (struct? coll) (tuple? coll) (array? coll)) (length coll)
|
||||||
# count is undefined on scalars (numbers/keywords/symbols/booleans/chars)
|
# count is undefined on scalars (numbers/keywords/symbols/booleans/chars)
|
||||||
(error (string "count not supported on " (type coll)))))
|
(error (string "count not supported on " (type coll))))))
|
||||||
|
|
||||||
(defn core-first [coll]
|
(defn core-first [coll]
|
||||||
(cond
|
(cond
|
||||||
|
|
@ -375,31 +370,34 @@
|
||||||
(pl-cons x (realize-for-iteration coll))))
|
(pl-cons x (realize-for-iteration coll))))
|
||||||
|
|
||||||
(defn core-seq [coll]
|
(defn core-seq [coll]
|
||||||
(cond
|
(if (table? coll)
|
||||||
(shape-rec? coll) (tuple ;(map (fn [k] (tuple k (shape-get coll k nil))) (shape-keys coll)))
|
(case (get coll :jolt/type)
|
||||||
(core-sorted? coll) ((sorted-op coll :seq) coll)
|
:jolt/pvec (if (= 0 (pv-count coll)) nil (tuple ;(pv->array coll)))
|
||||||
(or (nil? coll) (and (or (tuple? coll) (array? coll)) (= 0 (length coll)))) nil
|
|
||||||
# Cell-based emptiness, NOT (nil? (ls-first)): a lazy-seq whose first element
|
|
||||||
# is legitimately nil is non-empty, so (seq (cons nil ...)) must not be nil.
|
|
||||||
(lazy-seq? coll) (let [cell (realize-ls coll)]
|
|
||||||
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell))) nil coll))
|
|
||||||
(pvec? coll) (if (= 0 (pv-count coll)) nil (tuple ;(pv->array coll)))
|
|
||||||
(plist? coll) (if (pl-empty? coll) nil (tuple ;(pl->array coll)))
|
|
||||||
(buffer? coll) (if (= 0 (length coll)) nil (let [a @[]] (each x coll (array/push a x)) (tuple ;a)))
|
|
||||||
# empty maps/sets seq to nil, as in Clojure ((seq {}) is nil, not ())
|
# empty maps/sets seq to nil, as in Clojure ((seq {}) is nil, not ())
|
||||||
(set? coll) (if (= 0 (coll :cnt)) nil (phs-seq coll))
|
:jolt/phm (if (= 0 (coll :cnt)) nil (tuple ;(phm-entries coll)))
|
||||||
(phm? coll) (if (= 0 (coll :cnt)) nil (tuple ;(phm-entries coll)))
|
:jolt/plist (if (pl-empty? coll) nil (tuple ;(pl->array coll)))
|
||||||
(tuple? coll) (tuple/slice coll)
|
# Cell-based emptiness, NOT (nil? (ls-first)): a lazy-seq whose first
|
||||||
|
# element is legitimately nil is non-empty, so (seq (cons nil ...)) is not nil.
|
||||||
|
:jolt/lazy-seq (let [cell (realize-ls coll)]
|
||||||
|
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell))) nil coll))
|
||||||
|
:jolt/set (if (= 0 (coll :cnt)) nil (phs-seq coll))
|
||||||
|
:jolt/sorted-map ((sorted-op coll :seq) coll)
|
||||||
|
:jolt/sorted-set ((sorted-op coll :seq) coll)
|
||||||
|
# deftype instance seqs as itself; raw host table (System/getenv) as kv map
|
||||||
|
(if (get coll :jolt/deftype) coll
|
||||||
|
(if (= 0 (length coll)) nil
|
||||||
|
(tuple ;(map (fn [k] (tuple k (get coll k))) (keys coll))))))
|
||||||
|
(cond
|
||||||
|
# shape-recs are tuples — must precede the tuple? branch
|
||||||
|
(shape-rec? coll) (tuple ;(map (fn [k] (tuple k (shape-get coll k nil))) (shape-keys coll)))
|
||||||
|
(nil? coll) nil
|
||||||
|
(buffer? coll) (if (= 0 (length coll)) nil (let [a @[]] (each x coll (array/push a x)) (tuple ;a)))
|
||||||
|
(tuple? coll) (if (= 0 (length coll)) nil (tuple/slice coll))
|
||||||
(string? coll) (if (= 0 (length coll)) nil (tuple ;(map make-char (string/bytes coll))))
|
(string? coll) (if (= 0 (length coll)) nil (tuple ;(map make-char (string/bytes coll))))
|
||||||
(struct? coll) (if (= 0 (length coll)) nil (tuple ;(map (fn [k] (tuple k (get coll k))) (keys coll))))
|
(struct? coll) (if (= 0 (length coll)) nil (tuple ;(map (fn [k] (tuple k (get coll k))) (keys coll))))
|
||||||
(array? coll) (tuple ;coll)
|
(array? coll) (if (= 0 (length coll)) nil (tuple ;coll))
|
||||||
(and (table? coll) (get coll :jolt/deftype)) coll
|
|
||||||
# raw host table (System/getenv result) seqs like a map: kv entries
|
|
||||||
(and (table? coll) (nil? (get coll :jolt/type)))
|
|
||||||
(if (= 0 (length coll)) nil
|
|
||||||
(tuple ;(map (fn [k] (tuple k (get coll k))) (keys coll))))
|
|
||||||
# scalars/functions aren't seqable
|
# scalars/functions aren't seqable
|
||||||
(error (string "seq not supported on " (type coll)))))
|
(error (string "seq not supported on " (type coll))))))
|
||||||
|
|
||||||
(defn core-vec [coll]
|
(defn core-vec [coll]
|
||||||
(when (not (or (nil? coll) (core-coll? coll) (string? coll)))
|
(when (not (or (nil? coll) (core-coll? coll) (string? coll)))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue