Skip to content

Commit 8258f28

Browse files
committed
Merge pull request #181 from metosin/memoized_coercion
Memoize schema coercion
2 parents 2dad643 + cd778ad commit 8258f28

3 files changed

Lines changed: 130 additions & 6 deletions

File tree

project.clj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@
3737
[com.stuartsierra/component "0.3.1"]
3838
[reloaded.repl "0.2.1"]
3939
[http-kit "2.1.19"]
40+
[criterium "0.4.3"]
4041
; Required when using with Java 1.6
4142
[org.codehaus.jsr166-mirror/jsr166y "1.7.0"]]
4243
:ring {:handler examples.thingie/app
4344
:reload-paths ["src" "examples/src"]}
4445
:source-paths ["examples/src" "examples/dev-src"]
4546
:main examples.server}
47+
:perf {:jvm-opts ^:replace []}
4648
:logging {:dependencies [[org.clojure/tools.logging "0.3.1"]]}
4749
:1.8 {:dependencies [[org.clojure/clojure "1.8.0-RC2"]]}}
4850
:eastwood {:namespaces [:source-paths]
@@ -55,5 +57,6 @@
5557
"start-thingie" ["run"]
5658
"aot-uberjar" ["with-profile" "uberjar" "do" "clean," "ring" "uberjar"]
5759
"test-ancient" ["midje"]
60+
"perf" ["with-profile" "default,dev,perf"]
5861
"deploy!" ^{:doc "Recompile sources, then deploy if tests succeed."}
5962
["do" ["clean"] ["midje"] ["deploy" "clojars"]]})

src/compojure/api/meta.clj

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
[plumbing.core :refer :all]
88
[plumbing.fnk.impl :as fnk-impl]
99
[ring.swagger.common :refer :all]
10-
[ring.swagger.schema :as schema]
1110
[ring.swagger.json-schema :as js]
1211
[ring.util.http-response :refer [internal-server-error]]
1312
[slingshot.slingshot :refer [throw+]]
1413
[schema.core :as s]
14+
[schema.coerce :as sc]
15+
[schema.utils :as su]
1516
[schema-tools.core :as st]))
1617

1718
;;
@@ -23,7 +24,7 @@
2324
'+compojure-api-request+)
2425

2526
(def +compojure-api-meta+
26-
"lexically bound meta-data for handlers. EXPERIMENTAL."
27+
"lexically bound meta-data for handlers."
2728
'+compojure-api-meta+)
2829

2930
(defmacro meta-container [meta & form]
@@ -46,6 +47,8 @@
4647
;; Schema
4748
;;
4849

50+
(def memoized-coercer (memoize sc/coercer))
51+
4952
(defn strict [schema]
5053
(dissoc schema 'schema.core/Keyword))
5154

@@ -59,8 +62,9 @@
5962
(if-let [{:keys [status] :as response} (handler request)]
6063
(if-let [schema (:schema (responses status))]
6164
(if-let [matcher (:response (mw/get-coercion-matcher-provider request))]
62-
(let [body (schema/coerce schema (:body response) matcher)]
63-
(if (schema/error? body)
65+
(let [coerce (memoized-coercer (value-of schema) matcher)
66+
body (coerce (:body response))]
67+
(if (su/error? body)
6468
(throw+ (assoc body :type ::ex/response-validation))
6569
(assoc response
6670
::serializable? true
@@ -75,8 +79,9 @@
7579
(assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead."))
7680
`(let [value# (keywordize-keys (~key ~+compojure-api-request+))]
7781
(if-let [matcher# (~type (mw/get-coercion-matcher-provider ~+compojure-api-request+))]
78-
(let [result# (schema/coerce ~schema value# matcher#)]
79-
(if (schema/error? result#)
82+
(let [coerce# (memoized-coercer ~schema matcher#)
83+
result# (coerce# value#)]
84+
(if (su/error? result#)
8085
(throw+ (assoc result# :type ::ex/request-validation))
8186
result#))
8287
value#)))

test/compojure/api/perf_test.clj

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
(ns compojure.api.perf-test
2+
(:require [compojure.api.sweet :refer :all]
3+
[compojure.api.test-utils :refer :all]
4+
[criterium.core :as cc]
5+
[ring.util.http-response :refer :all]
6+
[schema.core :as s]))
7+
8+
;;
9+
;; start repl with `lein perf repl`
10+
;; perf measured with the following setup:
11+
;;
12+
;; Model Name: MacBook Pro
13+
;; Model Identifier: MacBookPro11,3
14+
;; Processor Name: Intel Core i7
15+
;; Processor Speed: 2,5 GHz
16+
;; Number of Processors: 1
17+
;; Total Number of Cores: 4
18+
;; L2 Cache (per Core): 256 KB
19+
;; L3 Cache: 6 MB
20+
;; Memory: 16 GB
21+
;;
22+
23+
(defn title [s]
24+
(println
25+
(str "\n\u001B[35m"
26+
(apply str (repeat (+ 6 (count s)) "#"))
27+
"\n## " s " ##\n"
28+
(apply str (repeat (+ 6 (count s)) "#"))
29+
"\u001B[0m\n")))
30+
31+
(s/defschema Order {:id s/Str
32+
:name s/Str
33+
(s/optional-key :description) s/Str
34+
:address (s/maybe {:street s/Str
35+
:country (s/enum "FI" "PO")})
36+
:orders [{:name #"^k"
37+
:price s/Any
38+
:shipping s/Bool}]})
39+
40+
(defn bench []
41+
42+
43+
(let [app (api
44+
(GET* "/30" []
45+
(ok {:result 30})))
46+
call #(get* app "/30")]
47+
48+
(title "GET JSON")
49+
50+
(assert (= {:result 30} (second (call))))
51+
(cc/bench (call)))
52+
53+
; 26µs => 26µs (-0%)
54+
55+
(let [app (api
56+
(POST* "/plus" []
57+
:return {:result s/Int}
58+
:body-params [x :- s/Int, y :- s/Int]
59+
(ok {:result (+ x y)})))
60+
data (json {:x 10, :y 20})
61+
call #(post* app "/plus" data)]
62+
63+
(title "JSON POST with 2-way coercion")
64+
65+
(assert (= {:result 30} (second (call))))
66+
(cc/bench (call)))
67+
68+
;; 87µs => 65µs (-25%)
69+
70+
(let [app (api
71+
(context* "/a" []
72+
(context* "/b" []
73+
(context* "/c" []
74+
(POST* "/plus" []
75+
:return {:result s/Int}
76+
:body-params [x :- s/Int, y :- s/Int]
77+
(ok {:result (+ x y)}))))))
78+
data (json {:x 10, :y 20})
79+
call #(post* app "/a/b/c/plus" data)]
80+
81+
(title "JSON POST with 2-way coercion + contexts")
82+
83+
(assert (= {:result 30} (second (call))))
84+
(cc/bench (call)))
85+
86+
;; 102µs => 78µs (-24%)
87+
88+
(let [app (api
89+
(POST* "/echo" []
90+
:return Order
91+
:body [order Order]
92+
(ok order)))
93+
data (json {:id "123"
94+
:name "Tommi's order"
95+
:description "Totally great order"
96+
:address {:street "Randomstreet 123"
97+
:country "FI"}
98+
:orders [{:name "k1"
99+
:price 123.0
100+
:shipping true}
101+
{:name "k2"
102+
:price 42.0
103+
:shipping false}]})
104+
call #(post* app "/echo" data)]
105+
106+
(title "JSON POST with nested data")
107+
108+
(s/check Order (second (call)))
109+
(cc/bench (call)))
110+
111+
;; 311µs => 194µs (-38%)
112+
113+
)
114+
115+
(comment
116+
(bench))

0 commit comments

Comments
 (0)