wrap
Map Performance and Recent ImprovementsThis document outlines the performance of wrap
maps in ClojureScript (tested on Node.js), reflecting optimizations including multi-deftype implementation and the use of defrecord
for implementation maps. If you're looking for the Clojure benchmarks look here.
The initial results were recorded before major architectural changes. Significant optimizations were made leading up to the latest benchmarks. This table shows the relative performance improvement between these runs. I Gemini collate the numbers into this table.
Relative Speed % = (Standard Map Time / Wrap Map Time) * 100%
Improvement % = ((Speed % Run 8 / Speed % Run 1) - 1) * 100%
Benchmark Operation | Original Speed (%) | Speed Now (%) | Overall Change (%) |
---|---|---|---|
Read Existing Key (Large Map) | 57.5% | 80% | +39.1% |
Read Missing Key | 51.0% | 66% | +29.4% |
Write (Update Existing Key - Large Map) | 52.7% | 92% | +74.6% |
Reduce (Sum Values - Large Map) | 107.8% | 97% | -10.0% |
Simple assoc (Baseline Wrap - Small) | 29.0% | 102% | +251.7% |
Simple assoc (Logging Wrap - Small) | 28.9% | 100% | +246.0% |
assoc New Key (Baseline Wrap - Large) | 52.5% | 96% | +82.9% |
assoc New Key (Validated Wrap - Large) | 53.3% | 92% | +72.6% |
Batch assoc! (Baseline Wrap) | 77.7% | 97% | +24.8% |
Batch assoc! (Logging Wrap) | 77.6% | 97%* | +25.0% |
persistent! Cost | 13.2% | 48% | +263.6% |
As shown, massive improvements were achieved, particularly for assoc
operations which now often meet or exceed standard map performance. Transient batch operations are also highly competitive. The main remaining bottleneck relative to standard maps in CLJS is the persistent!
cost.
Setup code similar to the Clojure benchmarks is used.
(ns ex.cljs-bench-setup ;; Example namespace
(:require
[com.jolygon.wrap-map :as w :refer [wrap empty-wrap freeze]]))
(do
;; baseline
(def small-std-map {:a 1 :b 2 :c 3})
(def small-wrap-map (wrap :a 1 :b 2 :c 3))
(def frozen-small-wrap-map (w/freeze small-wrap-map))
(def large-map-size 10000)
(def large-std-map (into {} (mapv (fn [i] [(keyword (str "k" i)) i]) (range large-map-size))))
(def large-wrap-map (doall (into w/empty-wrap large-std-map)))
(def frozen-large-wrap-map (w/freeze large-wrap-map))
(def keys-to-access (vec (keys large-std-map)))
(defn rand-key [] (rand-nth keys-to-access))
;; Overrides
(def log-atom (atom 0))
(defn logging-assoc-impl [{:as e :keys [<-]} m k v]
(swap! log-atom inc)
(<- e (assoc m k v)))
(defn validating-assoc-impl [{:as e :keys [<-]} m k v]
(if (keyword? k)
(<- e (assoc m k v))
(throw (ex-info "Invalid key" {:key k}))))
(def logged-wrap-map (w/assoc small-wrap-map :assoc_k_v logging-assoc-impl))
(def frozen-logged-wrap-map (w/freeze logged-wrap-map))
(def validated-wrap-map (w/assoc large-wrap-map :assoc_k_v validating-assoc-impl))
(def frozen-validated-wrap-map (w/freeze validated-wrap-map))
(def large-map-data (vec (mapcat (fn [i] [(keyword (str "k" i)) i]) (range large-map-size))))
(def items-to-add (vec (range large-map-size)))
:end)
(get large-wrap-map (rand-key))
|--------------------| | Wrap (80%)
|----------------| | Frozen Wrap (67%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(get large-wrap-map :not-a-key :default-val)
|----------------| | Wrap (66%) |---------------| | Frozen Wrap (65%) |-------------------------| Std (100%) 0% 25% 50% 75% 100%
(assoc large-wrap-map (rand-key) 999)
|----------------------| | Wrap (92%)
|-------------------------| Frozen Wrap (102%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(reduce-kv (fn [acc _ v] (+ acc v)) 0 large-wrap-map)
|-----------------------| | Wrap (97%)
|------------------------| Wrap (98%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(into w/empty-wrap (mapv vec (partition 2 large-map-data)))
|-----------------------| | Wrap (95%)
|----------------------| | Frozen Wrap (91%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(apply w/wrap large-map-data)
|-------------------| | Wrap (78%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(Note: No frozen apply constructor tested)
(assoc small-wrap-map :d 4)
|-------------------------| Wrap (Baseline - 102%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(assoc logged-wrap-map :d 4)
|-------------------------| Wrap (Logging - 100%)
|-------------------------| Frozen Wrap (Logging - 102%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(assoc validated-wrap-map :new-key 123)
|----------------------| | Wrap (Validated - 92%)
|------------------------| Frozen Wrap (Validated - 98%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(assoc large-wrap-map :new-key 123)
|-----------------------| | Wrap (Baseline - 96%)
|-----------------------| | Frozen Wrap (Baseline - 95%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(persistent! (reduce (fn [t i] (assoc! t (keyword (str "new" i)) i)) (transient w/empty-wrap) items-to-add))
|-----------------------| | Wrap (Baseline - 97%)
|-----------------------| | Frozen Wrap (Baseline - 95%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
(persistent! (transient large-wrap-map))
|-----------| | Wrap (Baseline - 48%)
|------------| | Frozen Wrap (Baseline - 52%)
|-------------------------| Std (100%)
0% 25% 50% 75% 100%
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close