110 lines
3.6 KiB
Plaintext
110 lines
3.6 KiB
Plaintext
;; Walking a map, which is what flan_map_next exists for. Every other map
|
|
;; operation addresses one entry by hashing it; this is the only thing that
|
|
;; reads the block in order.
|
|
;;
|
|
;; Block order is the hash's order and not the insertion's, so nothing here
|
|
;; may depend on which entry comes first: the checks are a sum, a count and a
|
|
;; membership test, all of them order-free. That is not a weakness of the test,
|
|
;; it is the contract — a caller that wants an order sorts what it collected.
|
|
|
|
(defn sum-and-count [] Unit
|
|
(let [m (map-new i32 i32)]
|
|
(put m 1 10)
|
|
(put m 2 20)
|
|
(put m 3 30)
|
|
(put m 4 40)
|
|
(let [cur (i64 0)
|
|
k 0
|
|
v 0
|
|
keys 0
|
|
vals 0
|
|
n 0]
|
|
(while (map-next! m (addr cur) (addr k) (addr v))
|
|
(set keys (+ keys k))
|
|
(set vals (+ vals v))
|
|
(set n (+ n 1)))
|
|
(print n) (print " ") (print keys) (print " ") (print vals) (println ""))
|
|
(free m)))
|
|
|
|
;; A map that never allocated has no block at all, and one that allocated and
|
|
;; holds nothing has a block of nothing but zeroed hashes. Both walk zero
|
|
;; times, and they are different code paths to get there.
|
|
(defn the-empty-cases [] Unit
|
|
(let [m (map-new i32 i32)
|
|
cur (i64 0)
|
|
k 0
|
|
v 0
|
|
n 0]
|
|
(while (map-next! m (addr cur) (addr k) (addr v))
|
|
(set n (+ n 1)))
|
|
(print "never allocated: ") (print n) (println "")
|
|
(reserve m 64)
|
|
(set cur (i64 0))
|
|
(while (map-next! m (addr cur) (addr k) (addr v))
|
|
(set n (+ n 1)))
|
|
(print "allocated and empty: ") (print n) (println "")
|
|
(free m)))
|
|
|
|
;; A cursor left past the end keeps answering false rather than wrapping, so a
|
|
;; second loop over a spent cursor is empty and not a repeat.
|
|
(defn a-spent-cursor [] Unit
|
|
(let [m (map-new i32 i32)]
|
|
(put m 5 50)
|
|
(put m 6 60)
|
|
(let [cur (i64 0) k 0 v 0 n 0]
|
|
(while (map-next! m (addr cur) (addr k) (addr v))
|
|
(set n (+ n 1)))
|
|
(while (map-next! m (addr cur) (addr k) (addr v))
|
|
(set n (+ n 1)))
|
|
(print "spent: ") (print n) (println ""))
|
|
(free m)))
|
|
|
|
;; A string key and a struct value: the key run and the value run have
|
|
;; different element sizes and different cell packing, so this is the case that
|
|
;; would catch the two runs being indexed with one geometry.
|
|
(defstruct Point [x i32 y i32])
|
|
|
|
(defn wider-entries [] Unit
|
|
(let [m (map-new string Point)]
|
|
(put m "a" (Point {.x 1 .y 2}))
|
|
(put m "bb" (Point {.x 3 .y 4}))
|
|
(put m "ccc" (Point {.x 5 .y 6}))
|
|
(let [cur (i64 0)
|
|
k ""
|
|
v (Point {})
|
|
chars 0
|
|
xs 0
|
|
ys 0]
|
|
(while (map-next! m (addr cur) (addr k) (addr v))
|
|
(set chars (+ chars (len k)))
|
|
(set xs (+ xs (.x v)))
|
|
(set ys (+ ys (.y v))))
|
|
(print chars) (print " ") (print xs) (print " ") (print ys) (println ""))
|
|
(free m)))
|
|
|
|
;; Growth past the 75% threshold rehashes into a new block, so this walks a map
|
|
;; whose layout is nothing like its insertion order and at a capacity several
|
|
;; doublings past the minimum.
|
|
(defn after-growth [] Unit
|
|
(let [m (map-new i64 i64)]
|
|
(dotimes [i 500]
|
|
(put m (i64 i) (* (i64 i) 2)))
|
|
(let [cur (i64 0)
|
|
k (i64 0)
|
|
v (i64 0)
|
|
n 0
|
|
doubled 0]
|
|
(while (map-next! m (addr cur) (addr k) (addr v))
|
|
(set n (+ n 1))
|
|
(when (= v (* k 2)) (set doubled (+ doubled 1))))
|
|
(print n) (print " ") (print doubled) (print " ") (print (len m)) (println ""))
|
|
(free m)))
|
|
|
|
(defn main [] i32
|
|
(sum-and-count)
|
|
(the-empty-cases)
|
|
(a-spent-cursor)
|
|
(wider-entries)
|
|
(after-growth)
|
|
0)
|