Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up filter accesses like {% if a.b.c.d %}. #246

Merged
merged 7 commits into from
Jan 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions src/selmer/tags.clj
Original file line number Diff line number Diff line change
Expand Up @@ -121,28 +121,31 @@
[#(comparator (parse-double %) (parse-double p2)) p1 nil])))

(defn numeric-expression-evaluation [[comparator context-key1 context-key2]]
(fn [context-map]
(let [[value1 value2]
(cond
(and context-key1 context-key2)
[(not-empty ((compile-filter-body context-key1) context-map))
(not-empty ((compile-filter-body context-key2) context-map))]
context-key1
[(not-empty ((compile-filter-body context-key1) context-map))]
context-key2
[(not-empty ((compile-filter-body context-key2) context-map))])]
(cond
(and value1 value2)
(comparator value1 value2)
value1
(comparator value1)
value2
(comparator value2)))))
; Parse the filter bodies first and close over them.
; This makes them cached.
(let [l (when context-key1 (compile-filter-body context-key1))
r (when context-key2 (compile-filter-body context-key2))]
(fn [context-map]
(let [value1 (when context-key1 (not-empty (l context-map)))
value2 (when context-key2 (not-empty (r context-map)))]
(cond
(and value1 value2)
(comparator value1 value2)

value1
(comparator value1)

value2
(comparator value2))))))

(defn if-any-all-fn [op params]
; op is either the function "some" or "any"
(let [filters (map compile-filter-body params)]
(fn [context-map]
(op #{true} (map #(if-result (% context-map)) filters)))))
(fn if-any-all-runtime-test [context-map]
; We want to short-circuit here, in case
; the first arg is true for ANY, or false for ALL.
(op (fn [f] (-> context-map (f) (if-result)))
filters))))

(defn parse-eq-arg [^String arg-string]
(cond
Expand Down Expand Up @@ -205,7 +208,7 @@
(render-if render context-map condition success failure))))

(defn if-handler [params tag-content render rdr]
; The main idea of this function is to genreate a list of test conditions and corresponding contetn,
; The main idea of this function is to generate a list of test conditions and corresponding content,
; then going though them in order until a test is successful, and then returning the contents belonging to
; that test.

Expand Down
8 changes: 5 additions & 3 deletions src/selmer/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,16 @@
(alter-var-root #'*missing-value-formatter* (constantly missing-value-fn))
(alter-var-root #'*filter-missing-values* (constantly filter-missing-values)))

(defn- parse-long [^String s]
(when (re-matches #"\d+" s)
(Long/valueOf s)))

(defn fix-accessor
"Turns strings into keywords and strings like \"0\" into Longs
so it can access vectors as well as maps."
[ks]
(mapv (fn [^String s]
(try (Long/valueOf s)
(catch NumberFormatException _
(keyword s))))
(or (parse-long s) (keyword s)))
ks))

(defn parse-accessor
Expand Down
29 changes: 26 additions & 3 deletions test/selmer/benchmark.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
[selmer.parser :refer :all]
[selmer.filters :as filters]
[selmer.util :refer :all]
[criterium.core :as criterium])
(:import java.io.StringReader))
[criterium.core :as criterium]
[clojure.string :as string]
[selmer.parser :as parser]))

(def user (repeat 10 [{:name "test" }]))
(def user (repeat 10 [{:name "test"}]))

(def nested-for-context {:users (repeat 10 user)})

Expand Down Expand Up @@ -48,3 +49,25 @@
(filters/add-filter! :inc (fn [^String s] (str (inc (Integer/parseInt s)))))
(criterium/quick-bench
(render (str "{{bar" filter-chain "}}") {:bar "0"})))

(deftest ^:benchmark if-bench
(println "BENCH: Many . acceses in an if clause")
(criterium/quick-bench
(render (string/join "" (repeat 1000 "{% if p.a.a.a.a %}{% endif %}"))
{})))

(deftest ^:benchmark many-numeric-if-clauses-bench
(println "BENCH: for loop with a numeric if clause in it")
(reset! parser/templates {})
(cache-on!)
(render-file "templates/numerics.html" {:ps []})
(criterium/quick-bench
(render-file "templates/numerics.html" {:ps (repeat 10000 "x")})))

(deftest ^:benchmark many-any-if-clauses-bench
(println "BENCH: for loop with a if any clause in it")
(reset! parser/templates {})
(cache-on!)
(render-file "templates/any.html" {:products []})
(criterium/quick-bench
(render-file "templates/any.html" {:products (repeat 10000 {})})))
3 changes: 3 additions & 0 deletions test/templates/any.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% for p in products %}
{% if any p.a p.b p.c p.d p.e p.f p.g %}{% endif %}
{% endfor %}
4 changes: 4 additions & 0 deletions test/templates/numerics.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% for p in ps %}
{% if p.a > p.b %}
{% endif %}
{% endfor %}