Parse command line args to benchmarks.clj and treat them as benchmark names to run Add information on running benchmarks to DEVELOPER.md
357 lines
11 KiB
Clojure
357 lines
11 KiB
Clojure
(ns com.rpl.specter.benchmarks
|
|
(:use [com.rpl.specter]
|
|
[com.rpl.specter.transients])
|
|
(:require [clojure.walk :as walk]
|
|
[com.rpl.specter.impl :as i]
|
|
[criterium.core :as bench]))
|
|
|
|
(defn pretty-float3 [anum]
|
|
(format "%.3g" anum))
|
|
|
|
(defn mean [a-fn]
|
|
(-> a-fn (bench/benchmark* {}) :mean first (* 1000000)))
|
|
|
|
(defn compare-benchmark [afn-map]
|
|
(let [results (transform MAP-VALS mean afn-map)
|
|
[[_ best-time] & _ :as sorted] (sort-by last results)]
|
|
(println "\nMean(us)\tvs best\t\tCode")
|
|
(doseq [[k t] sorted]
|
|
(println (pretty-float3 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
|
|
|
|
(defmacro run-benchmark [name & exprs]
|
|
(let [only-benchmarks (set (filter some? *command-line-args*))
|
|
all-benchmarks? (empty? only-benchmarks)]
|
|
(if (or all-benchmarks? (contains? only-benchmarks name))
|
|
(let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
|
|
`(do
|
|
(println "Benchmark:" ~name)
|
|
(compare-benchmark ~afn-map)
|
|
(println "\n********************************\n"))))))
|
|
|
|
(defn specter-dynamic-nested-get [data a b c]
|
|
(select-any (keypath a b c) data))
|
|
|
|
|
|
(defn get-k [k] (fn [m next] (next (get m k))))
|
|
|
|
(def get-a-b-c
|
|
(reduce
|
|
(fn [curr afn]
|
|
(fn [structure]
|
|
(afn structure curr)))
|
|
[identity (get-k :c) (get-k :b) (get-k :a)]))
|
|
|
|
(let [data {:a {:b {:c 1}}}
|
|
p (comp-paths :a :b :c)]
|
|
(run-benchmark "get value in nested map"
|
|
(select-any [:a :b :c] data)
|
|
(select-any (keypath :a :b :c) data)
|
|
(select-one [:a :b :c] data)
|
|
(select-first [:a :b :c] data)
|
|
(select-one! [:a :b :c] data)
|
|
(compiled-select-any p data)
|
|
(specter-dynamic-nested-get data :a :b :c)
|
|
(get-in data [:a :b :c])
|
|
(get-a-b-c data)
|
|
(-> data :a :b :c identity)
|
|
(-> data (get :a) (get :b) (get :c))
|
|
(-> data :a :b :c)
|
|
(select-any [(keypath :a) (keypath :b) (keypath :c)] data)))
|
|
|
|
|
|
(let [data {:a {:b {:c 1}}}]
|
|
(run-benchmark "set value in nested map"
|
|
(assoc-in data [:a :b :c] 1)
|
|
(setval [:a :b :c] 1 data)))
|
|
|
|
|
|
;; because below 1.7 there is no update function
|
|
(defn- my-update [m k afn]
|
|
(assoc m k (afn (get m k))))
|
|
|
|
(defn manual-transform [m afn]
|
|
(my-update m :a
|
|
(fn [m2]
|
|
(my-update m2 :b
|
|
(fn [m3]
|
|
(my-update m3 :c afn))))))
|
|
|
|
(let [data {:a {:b {:c 1}}}]
|
|
(run-benchmark "update value in nested map"
|
|
(update-in data [:a :b :c] inc)
|
|
(transform [:a :b :c] inc data)
|
|
(manual-transform data inc)))
|
|
|
|
|
|
(defn map-vals-map-iterable [^clojure.lang.IMapIterable m afn]
|
|
(let [k-it (.keyIterator m)
|
|
v-it (.valIterator m)]
|
|
(loop [ret {}]
|
|
(if (.hasNext k-it)
|
|
(let [k (.next k-it)
|
|
v (.next v-it)]
|
|
(recur (assoc ret k (afn v))))
|
|
|
|
ret))))
|
|
|
|
|
|
(defn map-vals-map-iterable-transient [^clojure.lang.IMapIterable m afn]
|
|
(persistent!
|
|
(let [k-it (.keyIterator m)
|
|
v-it (.valIterator m)]
|
|
(loop [ret (transient {})]
|
|
(if (.hasNext k-it)
|
|
(let [k (.next k-it)
|
|
v (.next v-it)]
|
|
(recur (assoc! ret k (afn v))))
|
|
|
|
ret)))))
|
|
|
|
|
|
(let [data '(1 2 3 4 5)]
|
|
(run-benchmark "transform values of a list"
|
|
(transform ALL inc data)
|
|
(doall (sequence (map inc) data))
|
|
(reverse (into '() (map inc) data))
|
|
))
|
|
|
|
(let [data {:a 1 :b 2 :c 3 :d 4}]
|
|
(run-benchmark "transform values of a small map"
|
|
(into {} (for [[k v] data] [k (inc v)]))
|
|
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
|
|
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
|
|
(reduce-kv (fn [m k v] (assoc m k (inc v))) (empty data) data)
|
|
(transform [ALL LAST] inc data)
|
|
(transform MAP-VALS inc data)
|
|
(zipmap (keys data) (map inc (vals data)))
|
|
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
|
|
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
|
|
(map-vals-map-iterable data inc)
|
|
(map-vals-map-iterable-transient data inc)
|
|
))
|
|
|
|
|
|
(let [data (->> (for [i (range 1000)] [i i]) (into {}))]
|
|
(run-benchmark "transform values of large map"
|
|
(into {} (for [[k v] data] [k (inc v)]))
|
|
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
|
|
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
|
|
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient clojure.lang.PersistentHashMap/EMPTY) data))
|
|
(reduce-kv (fn [m k v] (assoc m k (inc v))) (empty data) data)
|
|
(transform [ALL LAST] inc data)
|
|
(transform MAP-VALS inc data)
|
|
(zipmap (keys data) (map inc (vals data)))
|
|
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
|
|
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
|
|
(map-vals-map-iterable data inc)
|
|
(map-vals-map-iterable-transient data inc)))
|
|
|
|
|
|
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
|
(run-benchmark "first value of a size 10 vector"
|
|
(first data)
|
|
(select-any ALL data)
|
|
(select-any FIRST data)
|
|
(select-first ALL data)
|
|
))
|
|
|
|
(let [data [1 2 3 4 5]]
|
|
(run-benchmark "map a function over a vector"
|
|
(vec (map inc data))
|
|
(mapv inc data)
|
|
(transform ALL inc data)
|
|
(into [] (map inc) data)))
|
|
|
|
|
|
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
|
(run-benchmark "filter a sequence"
|
|
(doall (filter even? data))
|
|
(filterv even? data)
|
|
(select [ALL even?] data)
|
|
(select-any (filterer even?) data)
|
|
(into [] (filter even?) data)))
|
|
|
|
|
|
(let [data [{:a 2 :b 2} {:a 1} {:a 4} {:a 6}]
|
|
xf (comp (map :a) (filter even?))]
|
|
(run-benchmark "even :a values from sequence of maps"
|
|
(select [ALL :a even?] data)
|
|
(->> data (mapv :a) (filter even?) doall)
|
|
(into [] (comp (map :a) (filter even?)) data)
|
|
(into [] xf data)))
|
|
|
|
|
|
(let [v (vec (range 1000))]
|
|
(run-benchmark "Append to a large vector"
|
|
(setval END [1] v)
|
|
(setval AFTER-ELEM 1 v)
|
|
(reduce conj v [1])
|
|
(conj v 1)))
|
|
|
|
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
|
(run-benchmark "prepend to a vector"
|
|
(vec (cons 0 data))
|
|
(setval BEFORE-ELEM 0 data)
|
|
(into [0] data)
|
|
))
|
|
|
|
(declarepath TreeValues)
|
|
|
|
(providepath TreeValues
|
|
(if-path vector?
|
|
[ALL TreeValues]
|
|
STAY))
|
|
|
|
(defprotocolpath TreeValuesProt)
|
|
|
|
(extend-protocolpath TreeValuesProt
|
|
clojure.lang.PersistentVector [ALL TreeValuesProt]
|
|
Object STAY)
|
|
|
|
|
|
(defn tree-value-transform [afn atree]
|
|
(if (vector? atree)
|
|
(mapv #(tree-value-transform afn %) atree)
|
|
(afn atree)))
|
|
|
|
|
|
(let [data [1 2 [[3]] [4 6 [7 [8]] 10]]]
|
|
(run-benchmark "update every value in a tree (represented with vectors)"
|
|
(walk/postwalk (fn [e] (if (and (number? e) (even? e)) (inc e) e)) data)
|
|
(transform [(walker number?) even?] inc data)
|
|
(transform [TreeValues even?] inc data)
|
|
(transform [TreeValuesProt even?] inc data)
|
|
(tree-value-transform (fn [e] (if (even? e) (inc e) e)) data)))
|
|
|
|
|
|
(let [toappend (range 1000)]
|
|
(run-benchmark "transient comparison: building up vectors"
|
|
(reduce (fn [v i] (conj v i)) [] toappend)
|
|
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
|
|
(setval END toappend [])
|
|
(setval END! toappend (transient []))))
|
|
|
|
(let [toappend (range 1000)]
|
|
(run-benchmark "transient comparison: building up vectors one at a time"
|
|
(reduce (fn [v i] (conj v i)) [] toappend)
|
|
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
|
|
(reduce (fn [v i] (setval END [i] v)) [] toappend)
|
|
(reduce (fn [v i] (setval END! [i] v)) (transient []) toappend)))
|
|
|
|
|
|
(let [data (vec (range 1000))
|
|
tdata (transient data)
|
|
tdata2 (transient data)]
|
|
(run-benchmark "transient comparison: assoc'ing in vectors"
|
|
(assoc data 600 0)
|
|
(assoc! tdata 600 0)
|
|
(setval (keypath 600) 0 data)
|
|
(setval (keypath! 600) 0 tdata2)))
|
|
|
|
(let [data (into {} (for [k (range 1000)]
|
|
[k (rand)]))
|
|
tdata (transient data)
|
|
tdata2 (transient data)]
|
|
(run-benchmark "transient comparison: assoc'ing in maps"
|
|
(assoc data 600 0)
|
|
(assoc! tdata 600 0)
|
|
(setval (keypath 600) 0 data)
|
|
(setval (keypath! 600) 0 tdata2)))
|
|
|
|
(defn modify-submap
|
|
[m]
|
|
(assoc m 0 1 458 89))
|
|
|
|
(let [data (into {} (for [k (range 1000)]
|
|
[k (rand)]))
|
|
tdata (transient data)]
|
|
(run-benchmark "transient comparison: submap"
|
|
(transform (submap [600 700]) modify-submap data)
|
|
(transform (submap! [600 700]) modify-submap tdata)))
|
|
|
|
(let [data {:x 1}
|
|
meta-map {:my :metadata}]
|
|
(run-benchmark "set metadata"
|
|
(with-meta data meta-map)
|
|
(setval META meta-map data)))
|
|
|
|
(let [data (with-meta {:x 1} {:my :metadata})]
|
|
(run-benchmark "get metadata"
|
|
(meta data)
|
|
(select-any META data)))
|
|
|
|
(let [data (with-meta {:x 1} {:my :metadata})]
|
|
(run-benchmark "vary metadata"
|
|
(vary-meta data assoc :y 2)
|
|
(setval [META :y] 2 data)))
|
|
|
|
(let [data (range 1000)]
|
|
(run-benchmark "Traverse into a set"
|
|
(set data)
|
|
(set (select ALL data))
|
|
(into #{} (traverse ALL data))
|
|
(persistent!
|
|
(reduce conj! (transient #{}) (traverse ALL data)))
|
|
(reduce conj #{} (traverse ALL data))))
|
|
|
|
|
|
(defn mult-10 [v] (* 10 v))
|
|
|
|
(let [data [1 2 3 4 5 6 7 8 9]]
|
|
(run-benchmark "multi-transform vs. consecutive transforms, one shared nav"
|
|
(->> data (transform [ALL even?] mult-10) (transform [ALL odd?] dec))
|
|
(multi-transform [ALL (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
|
|
|
|
|
|
(let [data [[1 2 3 4 :a] [5] [6 7 :b 8 9] [10 11 12 13]]]
|
|
(run-benchmark "multi-transform vs. consecutive transforms, three shared navs"
|
|
(->> data (transform [ALL ALL number? even?] mult-10) (transform [ALL ALL number? odd?] dec))
|
|
(multi-transform [ALL ALL number? (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
|
|
|
|
(let [data {:a 1 :b 2 :c 3 :d 4}]
|
|
(run-benchmark "namespace qualify keys of a small map"
|
|
(into {}
|
|
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
|
|
data)
|
|
(reduce-kv (fn [m k v] (assoc m (keyword (str *ns*) (name k)) v)) {} data)
|
|
(setval [MAP-KEYS NAMESPACE] (str *ns*) data)
|
|
))
|
|
|
|
|
|
(let [data (->> (for [i (range 1000)] [(keyword (str i)) i]) (into {}))]
|
|
(run-benchmark "namespace qualify keys of a large map"
|
|
(into {}
|
|
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
|
|
data)
|
|
(reduce-kv (fn [m k v] (assoc m (keyword (str *ns*) (name k)) v)) {} data)
|
|
(setval [MAP-KEYS NAMESPACE] (str *ns*) data)
|
|
))
|
|
|
|
(defnav walker-old [afn]
|
|
(select* [this structure next-fn]
|
|
(i/walk-select afn next-fn structure))
|
|
(transform* [this structure next-fn]
|
|
(i/walk-until afn next-fn structure)))
|
|
|
|
(let [data {:a [1 2 {:c '(3 4) :d {:e [1 2 3] 7 8 9 10}}]}]
|
|
(run-benchmark "walker vs. clojure.walk version"
|
|
(transform (walker number?) inc data)
|
|
(transform (walker-old number?) inc data)
|
|
))
|
|
|
|
(let [size 1000
|
|
middle-idx (/ size 2)
|
|
v -1
|
|
rng (range size)
|
|
data-vec (vec rng)
|
|
data-lst (apply list rng)]
|
|
(run-benchmark "before-index vs. srange in middle (vector)"
|
|
(setval (before-index middle-idx) v data-vec)
|
|
(setval (srange middle-idx middle-idx) [v] data-vec))
|
|
(run-benchmark "before-index vs. srange in middle (list)"
|
|
(setval (before-index middle-idx) v data-lst)
|
|
(setval (srange middle-idx middle-idx) [v] data-lst))
|
|
(run-benchmark "before-index at 0 vs. srange vs. cons (list)"
|
|
(setval (before-index 0) v data-lst)
|
|
(setval (srange 0 0) [v] data-lst)
|
|
(cons v data-lst)))
|