phm/phs: bulk bottom-up HAMT build for the map/set builders (jolt-5vsp) (#154)
into {}, frequencies, group-by, set, into #{} and persistent! all built
their result by folding an immutable assoc/conj per element — each call
rebuilt the O(log32 n) trie path and allocated a fresh wrapper. Add a
one-pass bottom-up HAMT builder (phm-from-pairs) and route the builders
through it, the map/set analog of the pvec bulk build in #153.
phm-from-pairs partitions entries by hash and constructs the bin/array/
collision nodes directly, with the same bin<=16 / array-node>=17 promotion
the incremental path uses — so the trie is byte-identical to one built by
phm-assoc (validated across the size and branching boundaries, including
hash collisions, duplicate keys and the nil key). persistent! map/set and
the set constructor bulk-build; into {} keeps the small-scalar-map-stays-a
-struct rule via bulk-map-from-pairs; frequencies/group-by switch to the
canonical transient form and ride the fast persistent!.
50k A/B: into {} 704->270ms, frequencies 582->160, set 615->241,
into #{} 702->240, group-by 1358->919 (bound on persistent vector conj).
Gate: conformance x3, full suite (4718 >= baseline), new maps/sets bulk
boundary specs.
Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
43e5426601
commit
1873736045
7 changed files with 182 additions and 18 deletions
|
|
@ -64,15 +64,18 @@
|
|||
|
||||
(defn println [& xs] (apply print xs) (__write "\n") nil)
|
||||
|
||||
;; Base is (hash-map), not the {} literal: a literal map is a struct that doesn't
|
||||
;; canonicalize collection keys across representations (a {:a 1} literal vs
|
||||
;; (hash-map :a 1) key), whereas a PHM does — so counting/grouping by collection
|
||||
;; value needs the PHM base (the prior Janet impl used make-phm for this reason).
|
||||
;; Transient accumulation (canonical JVM form): assoc! into a native-backed
|
||||
;; scratch table per element, then persistent! bulk-builds the HAMT once —
|
||||
;; instead of a fresh persistent assoc (full trie-path rebuild) per element.
|
||||
;; A transient map canonicalizes collection keys (it is canon-keyed, like a
|
||||
;; PHM), so counting/grouping by collection value still works across map reps.
|
||||
(defn frequencies [coll]
|
||||
(reduce (fn [counts x] (assoc counts x (inc (get counts x 0)))) (hash-map) coll))
|
||||
(persistent!
|
||||
(reduce (fn [counts x] (assoc! counts x (inc (get counts x 0)))) (transient {}) coll)))
|
||||
|
||||
(defn group-by [f coll]
|
||||
(reduce (fn [ret x] (let [k (f x)] (assoc ret k (conj (get ret k []) x)))) (hash-map) coll))
|
||||
(persistent!
|
||||
(reduce (fn [ret x] (let [k (f x)] (assoc! ret k (conj (get ret k []) x)))) (transient {}) coll)))
|
||||
|
||||
(defn not-empty [coll]
|
||||
(if (or (nil? coll) (zero? (count coll))) nil coll))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue