specter/scripts/benchmarks.clj

358 lines
11 KiB
Clojure
Raw Normal View History

2016-06-05 15:39:58 +00:00
(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]))
2016-06-05 15:39:58 +00:00
(defn pretty-float3 [anum]
(format "%.3g" anum))
(defn mean [a-fn]
(-> a-fn (bench/benchmark* {}) :mean first (* 1000000)))
2016-08-11 14:13:27 +00:00
(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")
2016-06-05 15:39:58 +00:00
(doseq [[k t] sorted]
(println (pretty-float3 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
2016-06-05 15:39:58 +00:00
(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"))))))
2016-08-11 14:13:27 +00:00
(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)]))
2016-06-05 15:39:58 +00:00
(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)
2016-06-07 14:18:20 +00:00
(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)
2016-06-05 15:39:58 +00:00
(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)
2016-08-11 14:13:27 +00:00
(select-any [(keypath :a) (keypath :b) (keypath :c)] data)))
2016-06-05 15:39:58 +00:00
2017-01-27 15:39:50 +00:00
(let [data {:a {:b {:c 1}}}]
(run-benchmark "set value in nested map"
2017-01-27 15:39:50 +00:00
(assoc-in data [:a :b :c] 1)
(setval [:a :b :c] 1 data)))
2016-06-05 15:39:58 +00:00
;; 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"
2016-06-05 15:39:58 +00:00
(update-in data [:a :b :c] inc)
(transform [:a :b :c] inc data)
2016-08-11 14:13:27 +00:00
(manual-transform data inc)))
2016-06-05 15:39:58 +00:00
2016-06-15 16:23:03 +00:00
(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)]
2016-08-11 14:13:27 +00:00
(recur (assoc ret k (afn v))))
ret))))
2016-06-15 16:23:03 +00:00
(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)]
2016-08-11 14:13:27 +00:00
(recur (assoc! ret k (afn v))))
ret)))))
2016-06-15 16:23:03 +00:00
(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))
))
2016-06-15 14:31:59 +00:00
(let [data {:a 1 :b 2 :c 3 :d 4}]
(run-benchmark "transform values of a small map"
2016-06-15 14:31:59 +00:00
(into {} (for [[k v] data] [k (inc v)]))
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
2016-06-15 15:02:07 +00:00
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
2016-06-15 14:31:59 +00:00
(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)))
2016-06-15 16:23:03 +00:00
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
2016-06-15 16:23:03 +00:00
(map-vals-map-iterable data inc)
(map-vals-map-iterable-transient data inc)
))
2016-08-11 14:13:27 +00:00
2016-06-15 14:31:59 +00:00
(let [data (->> (for [i (range 1000)] [i i]) (into {}))]
(run-benchmark "transform values of large map"
2016-06-15 14:31:59 +00:00
(into {} (for [[k v] data] [k (inc v)]))
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
2016-06-15 15:02:07 +00:00
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
2016-06-15 16:23:03 +00:00
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient clojure.lang.PersistentHashMap/EMPTY) data))
2016-06-15 14:31:59 +00:00
(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)))
2016-06-15 16:23:03 +00:00
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
2016-06-15 16:23:03 +00:00
(map-vals-map-iterable data inc)
2016-08-11 14:13:27 +00:00
(map-vals-map-iterable-transient data inc)))
2016-06-15 14:31:59 +00:00
(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)
))
2016-06-05 15:39:58 +00:00
(let [data [1 2 3 4 5]]
(run-benchmark "map a function over a vector"
2016-06-05 15:39:58 +00:00
(vec (map inc data))
(mapv inc data)
2016-09-06 23:32:17 +00:00
(transform ALL inc data)
(into [] (map inc) data)))
2016-08-11 14:13:27 +00:00
2016-06-05 15:39:58 +00:00
(let [data [1 2 3 4 5 6 7 8 9 10]]
(run-benchmark "filter a sequence"
(doall (filter even? data))
2016-06-07 04:49:52 +00:00
(filterv even? data)
(select [ALL even?] data)
2016-09-06 23:32:17 +00:00
(select-any (filterer even?) data)
(into [] (filter even?) data)))
2016-08-11 14:13:27 +00:00
(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"
2016-06-07 13:40:14 +00:00
(select [ALL :a even?] data)
(->> data (mapv :a) (filter even?) doall)
(into [] (comp (map :a) (filter even?)) data)
2016-08-11 14:13:27 +00:00
(into [] xf data)))
2016-06-07 13:40:14 +00:00
2016-06-07 20:07:01 +00:00
(let [v (vec (range 1000))]
(run-benchmark "Append to a large vector"
2016-06-07 20:07:01 +00:00
(setval END [1] v)
(setval AFTER-ELEM 1 v)
2016-06-07 20:07:01 +00:00
(reduce conj v [1])
(conj v 1)))
2016-06-05 15:39:58 +00:00
(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)
))
2016-06-05 15:39:58 +00:00
(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)
2016-08-11 14:13:27 +00:00
(afn atree)))
2016-06-05 15:39:58 +00:00
(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)
2016-08-11 14:13:27 +00:00
(tree-value-transform (fn [e] (if (even? e) (inc e) e)) data)))
2016-06-05 15:39:58 +00:00
(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)
2016-08-11 14:13:27 +00:00
(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"
2016-06-08 17:58:15 +00:00
(meta data)
(select-any META data)))
2016-06-08 15:25:03 +00:00
(let [data (with-meta {:x 1} {:my :metadata})]
(run-benchmark "vary metadata"
(vary-meta data assoc :y 2)
(setval [META :y] 2 data)))
2016-06-09 20:36:20 +00:00
2016-06-15 13:31:24 +00:00
(let [data (range 1000)]
(run-benchmark "Traverse into a set"
2016-06-15 13:31:24 +00:00
(set data)
(set (select ALL data))
(into #{} (traverse ALL data))
(persistent!
(reduce conj! (transient #{}) (traverse ALL data)))
2016-08-11 14:13:27 +00:00
(reduce conj #{} (traverse ALL data))))
2016-06-15 13:31:24 +00:00
2016-06-23 16:30:50 +00:00
(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"
2016-06-23 16:30:50 +00:00
(->> data (transform [ALL even?] mult-10) (transform [ALL odd?] dec))
2016-08-11 14:13:27 +00:00
(multi-transform [ALL (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
2016-06-23 16:30:50 +00:00
(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"
2016-06-23 16:30:50 +00:00
(->> data (transform [ALL ALL number? even?] mult-10) (transform [ALL ALL number? odd?] dec))
2016-08-11 14:13:27 +00:00
(multi-transform [ALL ALL number? (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
2017-02-28 21:23:10 +00:00
(let [data {:a 1 :b 2 :c 3 :d 4}]
(run-benchmark "namespace qualify keys of a small map"
2017-02-28 21:23:10 +00:00
(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"
2017-02-28 21:23:10 +00:00
(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)))