Created
May 18, 2016 09:20
-
-
Save petterik/b6a77f3af473cdfb0237116ad0dd51ca to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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