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
|
|
@ -187,3 +187,24 @@
|
|||
["assoc-in deep get" "9" "(get-in (assoc-in {} [:a :b :c] 9) [:a :b :c])"]
|
||||
["seq over assoc-nil map" ":a" "(ffirst (seq (assoc nil :a 1)))"]
|
||||
["keys of assoc-nil map" "[:a]" "(vec (keys (assoc nil :a 1)))"])
|
||||
|
||||
# into {} / frequencies / group-by bulk-build the HAMT in one pass from a native
|
||||
# pairs/table accumulator (phm-from-pairs), instead of an assoc per element
|
||||
# (jolt-5vsp collections). Cross the bin(<=16)/array-node(>=17) promotion and a
|
||||
# size that grows the trie a level; check dedup (last-wins), nil keys, and that
|
||||
# assoc/dissoc after a bulk build still land. The structure is identical to the
|
||||
# incremental builder (validated in the PR), so reads agree.
|
||||
(defspec "map / bulk build boundaries"
|
||||
["into = incr at 17" "true" "(= (into {} (map (fn [i] [i (* i 2)]) (range 17))) (reduce (fn [m p] (assoc m (first p) (second p))) {} (map (fn [i] [i (* i 2)]) (range 17))))"]
|
||||
["into = incr at 1000" "true" "(= (into {} (map (fn [i] [i (* i 2)]) (range 1000))) (reduce (fn [m p] (assoc m (first p) (second p))) {} (map (fn [i] [i (* i 2)]) (range 1000))))"]
|
||||
["into count 1000" "1000" "(count (into {} (map (fn [i] [i i]) (range 1000))))"]
|
||||
["into reads back" "999" "(get (into {} (map (fn [i] [i (* i 3)]) (range 1000))) 333)"]
|
||||
["into onto non-empty" "9" "(get (into {:a 1} [[:a 9] [:b 2]]) :a)"]
|
||||
["into dup last wins" "9" "(get (into {} [[:k 1] [:k 9]]) :k)"]
|
||||
["into nil key" ":x" "(get (into {} [[nil :x] [:a 1]]) nil)"]
|
||||
["assoc after bulk" "7" "(get (assoc (into {} (map (fn [i] [i i]) (range 100))) :new 7) :new)"]
|
||||
["dissoc after bulk" "nil" "(get (dissoc (into {} (map (fn [i] [i i]) (range 100))) 50) 50)"]
|
||||
["frequencies count" "3" "(get (frequencies [1 2 2 1 2 1]) 1)"]
|
||||
["frequencies coll-key" "2" "(get (frequencies [[1 2] [1 2] [3 4]]) [1 2])"]
|
||||
["group-by bucket" "[1 3 5]" "(get (group-by odd? (range 1 6)) true)"]
|
||||
["hash-map bulk = incr" "true" "(= (apply hash-map (mapcat (fn [i] [i i]) (range 50))) (reduce (fn [m i] (assoc m i i)) {} (range 50)))"])
|
||||
|
|
|
|||
|
|
@ -56,3 +56,18 @@
|
|||
["vector is not" "false" "(set? [1])"]
|
||||
["coll? still true" "true" "(coll? (sorted-set 1))"]
|
||||
["ifn? sorted-set" "true" "(ifn? (sorted-set 1))"])
|
||||
|
||||
# set / into #{} bulk-build the backing HAMT in one pass (phs-from-seq), instead
|
||||
# of a phs-conj per element (jolt-5vsp collections). Cross the promotion
|
||||
# boundary, check dedup, collection members, and conj after a bulk build.
|
||||
(defspec "set / bulk build boundaries"
|
||||
["set dedup count" "3" "(count (set [1 1 2 3 3 2]))"]
|
||||
["set big count" "1000" "(count (set (range 1000)))"]
|
||||
["into #{} count" "500" "(count (into #{} (range 500)))"]
|
||||
["into #{} onto base" "3" "(count (into #{:a} [:a :b :c]))"]
|
||||
["set contains" "true" "(contains? (set (range 1000)) 777)"]
|
||||
["set missing" "false" "(contains? (set (range 1000)) 5000)"]
|
||||
["set coll members" "true" "(contains? (set [[1 2] [3 4]]) [1 2])"]
|
||||
["conj after bulk" "true" "(contains? (conj (set (range 100)) :x) :x)"]
|
||||
["disj after bulk" "false" "(contains? (disj (set (range 100)) 50) 50)"]
|
||||
["set = literal" "true" "(= #{0 1 2} (set (range 3)))"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue