core: lazy realization is shared across walks (once-only effects); pmap family
Every walk over a lazy seq created FRESH wrapper tables around the shared rest-thunks (ls-rest, ls-seq/ls-count, realize-for-iteration, the printers, reduce — each had its own make-lazy-seq loop), so independent walks re-ran the thunks: side effects duplicated, and a doall'd seq of futures was re-spawned serially by the deref walk. Every walker now goes through ls-rest-cached, which memoizes the rest wrapper on its node — thunks run exactly once, as in Clojure. Costs ~10% on walk-heavy benches (the per-node cache get/put — Clojure's LazySeq pays the same); net still -9% vs the pre-linear-walks baseline. Three regression rows pin once-only effects and value stability across walks. On top of that: pmap/pcalls/pvalues (jolt-oeu) over the real-thread futures — spawn-all-then-deref (the once-only fix is what makes the doall actually mean that), snapshot semantics documented, multi-coll arity via the canonical vector-zip. System/currentTimeMillis + nanoTime land as System statics (the realtime clock — os/time is whole seconds, which quantized every elapsed measurement to 1000ms). Seven pmap rows incl. a generous- margin parallelism check (4 x 200ms sleeps under 700ms after warmup).
This commit is contained in:
parent
599c0468f7
commit
4a1a9e3aec
8 changed files with 78 additions and 12 deletions
|
|
@ -191,6 +191,10 @@
|
|||
(let [msg (if message message (str "Assert failed: " (pr-str x)))]
|
||||
`(when-not ~x (throw (ex-info ~msg {})))))
|
||||
|
||||
;; (pvalues e1 e2 ...) — each expression evaluated in parallel (pcalls).
|
||||
(defmacro pvalues [& exprs]
|
||||
`(pcalls ~@(map (fn [e] `(fn [] ~e)) exprs)))
|
||||
|
||||
(defmacro delay [& body]
|
||||
`(make-delay (fn [] ~@body)))
|
||||
|
||||
|
|
|
|||
|
|
@ -175,3 +175,17 @@
|
|||
(lazy-seq
|
||||
(when-let [s (seq coll)]
|
||||
(cons (first s) (take-nth n (drop n s)))))))
|
||||
|
||||
;; --- pmap family (jolt-oeu): parallel map over real-thread futures ----------
|
||||
;; Each element's work runs on its own OS thread with SNAPSHOT semantics
|
||||
;; (futures marshal captured state — pure fns only, mutations don't propagate
|
||||
;; back). All futures are spawned up front (doall), then derefed in order:
|
||||
;; coarse-grained work only, as with Clojure's pmap.
|
||||
|
||||
(defn pmap
|
||||
([f coll]
|
||||
(map deref (doall (map (fn [x] (future (f x))) coll))))
|
||||
([f coll & colls]
|
||||
(pmap (fn [xs] (apply f xs)) (apply map vector coll colls))))
|
||||
|
||||
(defn pcalls [& fns] (pmap (fn [f] (f)) fns))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue