Compile Schema coercers ahead of time -> 4x perf

This commit is contained in:
Tommi Reiman 2017-12-01 09:32:26 +02:00
parent 71eae5fac2
commit e0dc618c4b
3 changed files with 138 additions and 8 deletions

View file

@ -53,12 +53,14 @@
(update :schema stringify)
(update :errors stringify)))
;; TODO: create all possible coercers ahead of time
(request-coercer [_ type schema]
(let [{:keys [formats default]} (matchers type)]
(let [{:keys [formats default]} (matchers type)
coercers (->> (for [m (conj (vals formats) default)]
[m (sc/coercer schema m)])
(into {}))]
(fn [value format]
(if-let [matcher (or (get formats format) default)]
(let [coercer (sc/coercer schema matcher)
(let [coercer (coercers matcher)
coerced (coercer value)]
(if-let [error (su/error-val coerced)]
(protocol/map->CoercionError

View file

@ -9,9 +9,15 @@
[reitit.ring :as ring]
[reitit.ring.coercion :as coercion]
[reitit.ring.coercion.spec :as spec]
[reitit.ring.coercion.schema :as schema]
[reitit.ring.coercion.protocol :as protocol]
[spec-tools.data-spec :as ds]
[reitit.core :as r]))
[muuntaja.middleware :as mm]
[muuntaja.core :as m]
[muuntaja.format.jsonista :as jsonista-format]
[jsonista.core :as j]
[reitit.core :as r])
(:import (java.io ByteArrayInputStream)))
;;
;; start repl with `lein perf repl`
@ -146,10 +152,6 @@
(comment
(do
(require '[reitit.core :as ring])
(require '[reitit.coercion :as coercion])
(require '[reitit.coercion.spec :as spec])
(def app
(ring/ring-handler
(ring/router
@ -173,3 +175,126 @@
:uri "/api/ping"
:body-params {:x 1, :y 2}}]
(cc/quick-bench (app req)))))
(defn json-perf-test []
(let [m (m/create (jsonista-format/with-json-format m/default-options))
app (ring/ring-handler
(ring/router
["/plus" {:post {:handler (fn [{{:keys [x y]} :body-params}]
{:status 200, :body {:result (+ x y)}})}}]
{:data {:middleware [[mm/wrap-format m]]}}))
request {:request-method :post
:uri "/plus"
:headers {"content-type" "application/json"}
:body (j/write-value-as-string {:x 1, :y 2})}
call (fn [] (-> request app :body slurp))]
(prn (-> request app :body slurp))
;; 7.8µs (cheshire)
;; 6.5µs (jsonista)
(cc/quick-bench
(-> request app :body slurp))))
(defn schema-json-perf-test []
(let [m (m/create (jsonista-format/with-json-format m/default-options))
app (ring/ring-handler
(ring/router
["/plus" {:post {:responses {200 {:schema {:result Long}}}
:parameters {:body {:x Long, :y Long}}
:handler (fn [request]
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [[mm/wrap-format m]
coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
:coercion schema/coercion}}))
request {:request-method :post
:uri "/plus"
:headers {"content-type" "application/json"}
:body (j/write-value-as-string {:x 1, :y 2})}
call (fn [] (-> request app :body slurp))]
(prn (-> request app :body slurp))
;; 11.6µs (cheshire)
;; 10.0µs (jsonista)
(cc/quick-bench
(-> request app :body slurp))))
(defn schema-perf-test []
(let [app (ring/ring-handler
(ring/router
["/plus" {:post {:responses {200 {:schema {:result Long}}}
:parameters {:body {:x Long, :y Long}}
:handler (fn [request]
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
:coercion schema/coercion}}))
request {:request-method :post
:uri "/plus"
:body-params {:x 1, :y 2}}
call (fn [] (-> request app :body))]
(assert (= {:result 3} (call)))
;; 0.23µs (no coercion)
;; 12.8µs
;; 1.9µs (cached coercers)
(cc/quick-bench
(call))))
(defn data-spec-perf-test []
(let [app (ring/ring-handler
(ring/router
["/plus" {:post {:responses {200 {:schema {:result int?}}}
:parameters {:body {:x int?, :y int?}}
:handler (fn [request]
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))
request {:request-method :post
:uri "/plus"
:body-params {:x 1, :y 2}}
call (fn [] (-> request app :body))]
(assert (= {:result 3} (call)))
;; 6.0µs
(cc/quick-bench
(call))))
(s/def ::x int?)
(s/def ::y int?)
(s/def ::request (s/keys :req-un [::x ::y]))
(s/def ::result int?)
(s/def ::response (s/keys :req-un [::result]))
(defn spec-perf-test []
(let [app (ring/ring-handler
(ring/router
["/plus" {:post {:responses {200 {:schema ::response}}
:parameters {:body ::request}
:handler (fn [request]
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))
request {:request-method :post
:uri "/plus"
:body-params {:x 1, :y 2}}
call (fn [] (-> request app :body))]
(assert (= {:result 3} (call)))
;; 3.2µs
(cc/quick-bench
(call))))
(comment
(json-perf-test)
(schema-json-perf-test)
(schema-perf-test)
(data-spec-perf-test)
(spec-perf-test))

View file

@ -45,6 +45,9 @@
[expound "0.3.2"]
[orchestra "2017.08.13"]
[metosin/muuntaja "0.4.1"]
[metosin/jsonista "0.1.0-SNAPSHOT"]
[criterium "0.4.4"]
[org.clojure/test.check "0.9.0"]
[org.clojure/tools.namespace "0.2.11"]