Functions and utilities for faster processing. | (ns metabase.util.performance
(:refer-clojure :exclude [reduce mapv run! some concat])
(:import (clojure.lang LazilyPersistentVector RT)
java.util.Iterator)) |
(set! *warn-on-reflection* true) | |
Like | (defn reduce
([f init coll1]
(if (nil? coll1)
init
(let [it1 (.iterator ^Iterable coll1)]
(loop [res init]
(if (.hasNext it1)
(let [res (f res (.next it1))]
(if (reduced? res)
@res
(recur res)))
res)))))
([f init coll1 coll2]
(if (or (nil? coll1) (nil? coll2))
init
(let [it1 (.iterator ^Iterable coll1)
it2 (.iterator ^Iterable coll2)]
(loop [res init]
(if (and (.hasNext it1) (.hasNext it2))
(let [res (f res (.next it1) (.next it2))]
(if (reduced? res)
@res
(recur res)))
res)))))
([f init coll1 coll2 coll3]
(if (or (nil? coll1) (nil? coll2) (nil? coll3))
init
(let [it1 (.iterator ^Iterable coll1)
it2 (.iterator ^Iterable coll2)
it3 (.iterator ^Iterable coll3)]
(loop [res init]
(if (and (.hasNext it1) (.hasNext it2) (.hasNext it3))
(let [res (f res (.next it1) (.next it2) (.next it3))]
(if (reduced? res)
@res
(recur res)))
res)))))
([f init coll1 coll2 coll3 coll4]
(if (or (nil? coll1) (nil? coll2) (nil? coll3) (nil? coll4))
init
(let [it1 (.iterator ^Iterable coll1)
it2 (.iterator ^Iterable coll2)
it3 (.iterator ^Iterable coll3)
it4 (.iterator ^Iterable coll4)]
(loop [res init]
(if (and (.hasNext it1) (.hasNext it2) (.hasNext it3) (.hasNext it4))
(let [res (f res (.next it1) (.next it2) (.next it3) (.next it4))]
(if (reduced? res)
@res
(recur res)))
res)))))) |
Special case for mapv. If the iterated collection has size <=32, it is more efficient to use object array as accumulator instead of transients, and then build a vector from it. | |
(definterface ISmallTransient (conj [x]) (persistent [])) | |
(deftype SmallTransientImpl [^objects arr, ^:unsynchronized-mutable ^long cnt]
ISmallTransient
(conj [this x]
(RT/aset arr (unchecked-int cnt) x)
(set! cnt (unchecked-inc cnt))
this)
(persistent [_]
(LazilyPersistentVector/createOwning arr))) | |
(defn- small-transient [n] (SmallTransientImpl. (object-array n) 0)) | |
(defn- small-conj!
{:inline (fn [st x] `(.conj ~(with-meta st {:tag `ISmallTransient}) ~x))}
[^ISmallTransient st x]
(.conj st x)) | |
(defn- small-persistent! [^ISmallTransient st] (.persistent st)) | |
(defn- smallest-count (^long [c1 c2] (min (count c1) (count c2))) (^long [c1 c2 c3] (min (count c1) (count c2) (count c3))) (^long [c1 c2 c3 c4] (min (count c1) (count c2) (count c3) (count c4)))) | |
Like | (defn mapv
([f coll1]
(let [n (count coll1)]
(cond (= n 0) []
(<= n 32) (small-persistent! (reduce #(small-conj! %1 (f %2)) (small-transient n) coll1))
:else (persistent! (reduce #(conj! %1 (f %2)) (transient []) coll1)))))
([f coll1 coll2]
(let [n (smallest-count coll1 coll2)]
(cond (= n 0) []
(<= n 32) (small-persistent! (reduce #(small-conj! %1 (f %2 %3)) (small-transient n) coll1 coll2))
:else (persistent! (reduce #(conj! %1 (f %2 %3)) (transient []) coll1 coll2)))))
([f coll1 coll2 coll3]
(let [n (smallest-count coll1 coll2 coll3)]
(cond (= n 0) []
(<= n 32) (small-persistent! (reduce #(small-conj! %1 (f %2 %3 %4)) (small-transient n) coll1 coll2 coll3))
:else (persistent! (reduce #(conj! %1 (f %2 %3 %4)) (transient []) coll1 coll2 coll3)))))
([f coll1 coll2 coll3 coll4]
(let [n (smallest-count coll1 coll2 coll3 coll4)]
(cond (= n 0) []
(<= n 32) (small-persistent! (reduce #(small-conj! %1 (f %2 %3 %4 %5)) (small-transient n) coll1 coll2 coll3 coll4))
:else (persistent! (reduce #(conj! %1 (f %2 %3 %4 %5)) (transient []) coll1 coll2 coll3 coll4)))))) |
Like | (defn run! ([f coll1] (reduce (fn [_ x] (f x)) nil coll1))) |
Like | (defn juxt*
[fns]
(let [fns (vec fns)]
(fn
([] (mapv #(%) fns))
([x] (mapv #(% x) fns))
([x y] (mapv #(% x y) fns))
([x y z] (mapv #(% x y z) fns))
([x y z & args] (mapv #(apply % x y z args) fns))))) |
Like | (defn some [f coll] (unreduced (reduce #(when-let [match (f %2)] (reduced match)) nil coll))) |
Like | (defn concat
([a b]
(into (vec a) b))
([a b c]
(as-> (transient (vec a)) res
(reduce conj! res b)
(reduce conj! res c)
(persistent! res)))
([a b c d]
(as-> (transient (vec a)) res
(reduce conj! res b)
(reduce conj! res c)
(reduce conj! res d)
(persistent! res)))
([a b c d e]
(as-> (transient (vec a)) res
(reduce conj! res b)
(reduce conj! res c)
(reduce conj! res d)
(reduce conj! res e)
(persistent! res)))
([a b c d e f]
(as-> (transient (vec a)) res
(reduce conj! res b)
(reduce conj! res c)
(reduce conj! res d)
(reduce conj! res e)
(reduce conj! res f)
(persistent! res)))
([a b c d e f & more]
(as-> (transient (vec a)) res
(reduce conj! res b)
(reduce conj! res c)
(reduce conj! res d)
(reduce conj! res e)
(reduce conj! res f)
(reduce (fn [res l] (reduce conj! res l)) res more)
(persistent! res)))) |
Like | (defn transpose
[coll-of-colls]
(let [its (mapv #(.iterator ^Iterable %) coll-of-colls)]
(mapv (fn [_] (mapv #(.next ^Iterator %) its))
(first coll-of-colls)))) |
clojure.walk reimplementation. Partially adapted from https://github.com/tonsky/clojure-plus. | |
(defn- editable? [coll] (instance? clojure.lang.IEditableCollection coll)) | |
(defn- transient? [coll] (instance? clojure.lang.ITransientCollection coll)) | |
(defn- assoc+ [coll key value]
(cond
(transient? coll) (assoc! coll key value)
(editable? coll) (assoc! (transient coll) key value)
:else (assoc coll key value))) | |
(defn- dissoc+ [coll key]
(cond
(transient? coll) (dissoc! coll key)
(editable? coll) (dissoc! (transient coll) key)
:else (dissoc coll key))) | |
(defn- maybe-persistent! [coll]
(cond-> coll
(transient? coll) persistent!)) | |
Like | (defn walk
[inner outer form]
(cond
(map? form)
(let [new-keys (volatile! (transient #{}))]
(-> (reduce-kv (fn [m k v]
(let [k' (inner k)
v' (inner v)]
(if (identical? k' k)
(if (identical? v' v)
m
(assoc+ m k' v'))
(do (vswap! new-keys conj! k')
(if (contains? @new-keys k)
(assoc+ m k' v')
(-> m (dissoc+ k) (assoc+ k' v')))))))
form form)
maybe-persistent!
(with-meta (meta form))
outer))
(vector? form)
(-> (reduce-kv (fn [v idx el]
(let [el' (inner el)]
(if (identical? el' el)
v
(assoc+ v idx el'))))
form form)
maybe-persistent!
(with-meta (meta form))
outer)
;; Don't care much about optimizing seq and generic coll cases. When efficiency is required, use vectors.
(seq? form) (outer (with-meta (seq (mapv inner form)) (meta form))) ;;
(coll? form) (outer (with-meta (into (empty form) (map inner) form) (meta form)))
:else (outer form))) |
Like | (defn prewalk [f form] (walk (fn prewalker [form] (walk prewalker identity (f form))) identity (f form))) |
Like | (defn postwalk [f form] (walk (fn postwalker [form] (walk postwalker f form)) f form)) |
Like | (defn keywordize-keys
[m]
(postwalk
(fn [form]
(if (map? form)
(-> (reduce-kv (fn [m k v]
(if (string? k)
(-> m (dissoc+ k) (assoc+ (keyword k) v))
m))
form form)
maybe-persistent!
(with-meta (meta form)))
form))
m)) |