specter/scripts/benchmarks.clj

337 lines
10 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 [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)
))