Skip to content

Instantly share code, notes, and snippets.

@petterik
Created May 18, 2016 09:20
Show Gist options
  • Save petterik/b6a77f3af473cdfb0237116ad0dd51ca to your computer and use it in GitHub Desktop.
Save petterik/b6a77f3af473cdfb0237116ad0dd51ca to your computer and use it in GitHub Desktop.
;; This is a stand alone version of https://github.com/petterik/om/commit/c99f1b892f0a143fba6ce57c3ffefc7ce1e2b9a0
;; Changed so that in path-meta, we're parsing the joins from a query one time for each data collection
;; instead of parsing joins for each item in the collection.
;; This gist contains implementation and a test.
;; It's runnable with planck by either downloading the gist as gist.cljs and passing it to planck
;; with the om source code:
;; planck -c "<path-to-om-project>/src/main" gist.cljs
(ns petterik.path-meta-perf
(:require-macros [cljs.test :refer [deftest is]])
(:require [cljs.test]
[om.next.impl.parser :as parser :refer [expr->ast]]
[om.util :as util]))
;; Impl
(defn- path-meta-joins
"returns a tripple with [key sel union-entry] for each join in a query."
[query union-expr]
(into [] (comp (filter #(or (util/join? %) (util/ident? %)))
(map #(cond-> % (util/ident? %) (hash-map '[*])))
(map (fn [join]
(let [[key sel] (util/join-entry join)
union-entry (if (util/union? join) sel union-expr)
sel (if (util/recursion? sel)
(if-not (nil? union-expr)
union-entry
query)
sel)
key (cond-> key (util/unique-ident? key) first)]
[key sel union-entry]))))
query))
(defn path-meta
"Add path metadata to a data structure. data is the data to be worked on.
path is the current path into the data. query is the query used to
walk the data. union-expr tracks the last seen union query to be used
when it finds a recursive union."
([data path query]
(path-meta data path query nil nil))
([data path query union-expr]
(path-meta data path query union-expr nil))
([data path query union-expr joins]
(cond
(nil? query)
(cond-> data
#?(:clj (instance? clojure.lang.IObj data)
:cljs (satisfies? IWithMeta data))
(vary-meta assoc :om-path path))
(sequential? data)
;; The joins will be the same for each item in the sequential data. Create them once.
(let [joins (when (vector? query) (path-meta-joins query union-expr))]
(-> (into []
(map-indexed
(fn [idx v]
(path-meta v (conj path idx) query union-expr joins)))
data)
(vary-meta assoc :om-path path)))
(vector? query)
(loop [joins (seq (or joins (path-meta-joins query union-expr))) ret data]
(if-not (nil? joins)
(let [[key sel union-entry] (first joins)]
(recur (next joins)
(cond-> ret
(contains? ret key)
(assoc key
(path-meta (get ret key) (conj path key) sel union-entry nil)))))
(cond-> ret
#?(:clj (instance? clojure.lang.IObj ret)
:cljs (satisfies? IWithMeta ret))
(vary-meta assoc :om-path path))))
:else
;; UNION
(if (map? data)
(let [dispatch-key (comp :dispatch-key expr->ast)
branches (vals query)
props (map dispatch-key (keys data))
query (some (fn [q]
(let [query-props (map dispatch-key q)]
(when (= (set props)
(set query-props))
q)))
branches)]
(path-meta data path query union-expr))
data))))
;; Test:
;; ----------------------------------------------------------------------------
;; Parser's path-meta
(defn bench [label thunk]
(prn "Running " label " 10 times")
(dotimes [_ 9]
(time (thunk)))
(let [ret (time (thunk))]
(prn "End " label)
ret))
(deftest test-path-meta-speed
(let [query [{:people [:gender
:age
{:name [:firstname :lastname]}
:likes
{:body [:height]}
:dislikes
{:friends [:likes]}
:more
:attrs
:and
:even..
:moar
{:joins [:with :stuff]}]}]
data {:people [{:gender "female" :age 28 :name {:firstname "Alex" :lastname "Is"} :likes :programming :body {:height 170}}
{:gender "male" :age 27 :name {:firstname "Bob" :lastname "Your uncle"} :likes :cljs :body {:height 170}}]}
more-data (update data :people #(->> % (cycle) (take 10000)))]
(is (= (parser/path-meta data [] query)
(path-meta data [] query)))
(is (= (bench "path-meta" #(parser/path-meta more-data [] query))
(bench "new-path-meta" #(path-meta more-data [] query))))))
(cljs.test/run-tests)
;; My test output:
"Running " "path-meta" " 10 times"
"Elapsed time: 312.000000 msecs"
"Elapsed time: 251.000000 msecs"
"Elapsed time: 217.000000 msecs"
"Elapsed time: 189.000000 msecs"
"Elapsed time: 196.000000 msecs"
"Elapsed time: 185.000000 msecs"
"Elapsed time: 189.000000 msecs"
"Elapsed time: 177.000000 msecs"
"Elapsed time: 165.000000 msecs"
"Elapsed time: 174.000000 msecs"
"End " "path-meta"
"Running " "new-path-meta" " 10 times"
"Elapsed time: 194.000000 msecs"
"Elapsed time: 125.000000 msecs"
"Elapsed time: 107.000000 msecs"
"Elapsed time: 160.000000 msecs"
"Elapsed time: 114.000000 msecs"
"Elapsed time: 114.000000 msecs"
"Elapsed time: 100.000000 msecs"
"Elapsed time: 120.000000 msecs"
"Elapsed time: 113.000000 msecs"
"Elapsed time: 134.000000 msecs"
"End " "new-path-meta"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment