Merge pull request #163 from metosin/perf_tuning

Perf tuning
This commit is contained in:
Tommi Reiman 2018-11-04 20:57:55 +02:00 committed by GitHub
commit 8f26ce8a32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 93 additions and 75 deletions

View file

@ -1,15 +1,3 @@
; Copyright 2013 Relevance, Inc.
; Copyright 2014-2016 Cognitect, Inc.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0)
; which can be found in the file epl-v10.html at the root of this distribution.
;
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
;
; You must not remove this notice, or any other, from this software.
(ns ^:no-doc reitit.impl (ns ^:no-doc reitit.impl
#?(:cljs (:require-macros [reitit.impl])) #?(:cljs (:require-macros [reitit.impl]))
(:require [clojure.string :as str] (:require [clojure.string :as str]
@ -19,17 +7,22 @@
(java.util HashMap Map) (java.util HashMap Map)
(java.net URLEncoder URLDecoder)))) (java.net URLEncoder URLDecoder))))
(defn map-kv (defn maybe-map-values
"Applies a function to every value of a map. "Applies a function to every value of a map, updates the value if not nil.
Also works on vectors. Maintains key for maps, order for vectors." Also works on vectors. Maintains key for maps, order for vectors."
[f coll] [f coll]
(reduce-kv (reduce-kv
(fn [m k v] (fn [coll k v]
(assoc m k (f v))) (if-some [v' (f v)]
(empty coll) (assoc coll k v')
coll))
coll
coll)) coll))
;;
;; https://github.com/pedestal/pedestal/blob/master/route/src/io/pedestal/http/route/prefix_tree.clj
;;
(defn wild? [s] (defn wild? [s]
(contains? #{\: \*} (first (str s)))) (contains? #{\: \*} (first (str s))))
@ -191,17 +184,19 @@
#?(:clj (str/replace s #"[^A-Za-z0-9\!'\(\)\*_~.-]+" percent-encode) #?(:clj (str/replace s #"[^A-Za-z0-9\!'\(\)\*_~.-]+" percent-encode)
:cljs (js/encodeURIComponent s)))) :cljs (js/encodeURIComponent s))))
(defn url-decode [s] (defn maybe-url-decode [s]
(if s (if s
#?(:clj (if (.contains ^String s "%") #?(:clj (if (.contains ^String s "%")
(URLDecoder/decode (URLDecoder/decode
(if (.contains ^String s "+") (if (.contains ^String s "+")
(.replace ^String s "+" "%2B") (.replace ^String s "+" "%2B")
s) s)
"UTF-8") "UTF-8"))
s)
:cljs (js/decodeURIComponent s)))) :cljs (js/decodeURIComponent s))))
(defn url-decode [s]
(or (maybe-url-decode s) s))
(defn form-encode [s] (defn form-encode [s]
(if s (if s
#?(:clj (URLEncoder/encode ^String s "UTF-8") #?(:clj (URLEncoder/encode ^String s "UTF-8")
@ -217,7 +212,7 @@
(defn url-decode-coll (defn url-decode-coll
"URL-decodes maps and vectors" "URL-decodes maps and vectors"
[coll] [coll]
(map-kv url-decode coll)) (maybe-map-values maybe-url-decode coll))
(defprotocol IntoString (defprotocol IntoString
(into-string [_])) (into-string [_]))
@ -251,7 +246,7 @@
(defn path-params (defn path-params
"Convert parameters' values into URL-encoded strings, suitable for URL paths" "Convert parameters' values into URL-encoded strings, suitable for URL paths"
[params] [params]
(map-kv #(url-encode (into-string %)) params)) (maybe-map-values #(url-encode (into-string %)) params))
(defn query-string (defn query-string
"shallow transform of query parameters into query string" "shallow transform of query parameters into query string"
@ -270,7 +265,7 @@
(goog/inherits ~type ~base-type) (goog/inherits ~type ~base-type)
~@(map ~@(map
(fn [method] (fn [method]
`(set! (.. ~type -prototype ~(symbol (str "-" (first method)))) `(set! (.. ~type -prototype ~(symbol (str "-" (first method))))
(fn ~@(rest method)))) (fn ~@(rest method))))
methods))) methods)))

View file

@ -15,16 +15,16 @@
(-lookup [_ _ _])) (-lookup [_ _ _]))
(defn- -catch-all [children catch-all path-params p ps] (defn- -catch-all [children catch-all path-params p ps]
(if catch-all (-lookup
(-lookup (impl/fast-get children catch-all)
(impl/fast-get children catch-all) nil
nil (assoc path-params catch-all (str/join "/" (cons p ps)))))
(assoc path-params catch-all (str/join "/" (cons p ps))))))
(defn- segment (defn- segment
([] (segment {} #{} nil nil)) ([] (segment {} #{} nil nil))
([children wilds catch-all match] ([children wilds catch-all match]
(let [children' (impl/fast-map children)] (let [children' (impl/fast-map children)
wilds? (seq wilds)]
^{:type ::segment} ^{:type ::segment}
(reify (reify
Segment Segment
@ -40,8 +40,8 @@
(if (nil? p) (if (nil? p)
(when match (assoc match :path-params path-params)) (when match (assoc match :path-params path-params))
(or (-lookup (impl/fast-get children' p) ps path-params) (or (-lookup (impl/fast-get children' p) ps path-params)
(some #(-lookup (impl/fast-get children' %) ps (assoc path-params % p)) wilds) (if wilds? (some #(-lookup (impl/fast-get children' %) ps (assoc path-params % p)) wilds))
(-catch-all children' catch-all path-params p ps)))))))) (if catch-all (-catch-all children' catch-all path-params p ps)))))))))
(defn insert [root path data] (defn insert [root path data]
(-insert (or root (segment)) (impl/segments path) (map->Match {:data data}))) (-insert (or root (segment)) (impl/segments path) (map->Match {:data data})))

View file

@ -2,8 +2,7 @@
(:require [criterium.core :as cc] (:require [criterium.core :as cc]
[reitit.perf-utils :refer :all] [reitit.perf-utils :refer :all]
[reitit.ring :as ring] [reitit.ring :as ring]
[clojure.string :as str] [clojure.string :as str]))
[reitit.core :as r]))
;; ;;
;; start repl with `lein perf repl` ;; start repl with `lein perf repl`
@ -313,20 +312,23 @@
;; 40ns (httprouter) ;; 40ns (httprouter)
;; 140ns ;; 140ns
;; 120ns (faster decode params)
(let [req (map->Req {:request-method :get, :uri "/user/repos"})] (let [req (map->Req {:request-method :get, :uri "/user/repos"})]
(title "static") (title "static")
(assert (= {:status 200, :body "/user/repos"} (app req))) (assert (= {:status 200, :body "/user/repos"} (app req)))
(cc/quick-bench (app req))) (cc/quick-bench (app req)))
;; 160ns (httprouter) ;; 160ns (httprouter)
;; 990ns ;; 990ns
;; 830ns (faster decode params)
(let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})] (let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})]
(title "param") (title "param")
(assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req))) (assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req)))
(cc/quick-bench (app req))) (cc/quick-bench (app req)))
;; 30µs (httprouter) ;; 30µs (httprouter)
;; 190µs ;; 190µs
;; 160µs (faster decode params)
(let [requests (mapv route->req routes)] (let [requests (mapv route->req routes)]
(title "all") (title "all")
(cc/quick-bench (cc/quick-bench

View file

@ -2,7 +2,8 @@
(:require [criterium.core :as cc] (:require [criterium.core :as cc]
[reitit.perf-utils :refer :all] [reitit.perf-utils :refer :all]
[ring.util.codec] [ring.util.codec]
[reitit.impl]) [reitit.impl]
[reitit.impl :as impl])
(:import (java.net URLDecoder URLEncoder))) (:import (java.net URLDecoder URLEncoder)))
;; ;;
@ -163,8 +164,30 @@
"1"]] "1"]]
(test! f s)))) (test! f s))))
(defn url-encode-coll! []
(suite "url-encode-coll")
;; 740ns
(test "something to decode")
(test! impl/url-decode-coll
{:a "aja%20hiljaa+sillalla"
:b "aja_hiljaa_sillalla"
:c "1+1"
:d "1"})
;; 124ns
;; 50ns (maybe-map-values)
(test "nothing to decode")
(test! impl/url-decode-coll
{:a "aja+20hiljaa+sillalla"
:b "aja_hiljaa_sillalla"
:c "1+1"
:d "1"}))
(comment (comment
(url-decode!) (url-decode!)
(url-encode!) (url-encode!)
(form-decode!) (form-decode!)
(form-encode!)) (form-encode!)
(url-encode-coll!))

View file

@ -440,6 +440,7 @@
;; 723ns (segment-router) ;; 723ns (segment-router)
;; 702ns (before path-parameters) ;; 702ns (before path-parameters)
;; 806ns (decode path-parameters) ;; 806ns (decode path-parameters)
;; 735ns (maybe-map-values)
(b! "reitit-ring" reitit-ring-f) (b! "reitit-ring" reitit-ring-f)
;; 2821ns ;; 2821ns

View file

@ -80,39 +80,36 @@
(defn bench! [] (defn bench! []
;; 2.3ms ;; 2.3µs
(cc/quick-bench
(dotimes [_ 1000]
(p/lookup pedestal-tree "/v1/orgs/1/topics")))
;; 3.1ms
;; 2.5ms (string equals)
;; 2.5ms (protocol)
;; 2.3ms (nil childs)
;; 2.0ms (rando impros)
;; 1.9ms (wild & catch shortcuts)
;; 1.5ms (inline child fetching)
;; 1.5ms (WildNode also backtracks)
;; 1.4ms (precalculate segment-size)
;; 1.3ms (fast-map)
;; 1.3ms (dissoc wild & catch-all from children)
;; 1.3ms (reified protocols)
;; 0.8ms (flattened matching)
;; 0.8ms (return route-data)
;; 0.8ms (fix payloads)
#_(cc/quick-bench #_(cc/quick-bench
(dotimes [_ 1000] (p/lookup pedestal-tree "/v1/orgs/1/topics"))
(trie/lookup reitit-tree "/v1/orgs/1/topics" {})))
;; 0.9ms (initial) ;; 3.1µs
;; 0.5ms (protocols) ;; 2.5µs (string equals)
;; 1.0ms (with path params) ;; 2.5µs (protocol)
;; 1.0ms (Match records) ;; 2.3µs (nil childs)
;; 0.63ms (Single sweep path params) ;; 2.0µs (rando impros)
;; 0.51ms (Cleanup) ;; 1.9µs (wild & catch shortcuts)
;; 1.5µs (inline child fetching)
;; 1.5µs (WildNode also backtracks)
;; 1.4µs (precalculate segment-size)
;; 1.3µs (fast-map)
;; 1.3µs (dissoc wild & catch-all from children)
;; 1.3µs (reified protocols)
;; 0.8µs (flattened matching)
;; 0.8µs (return route-data)
;; 0.8µs (fix payloads)
#_(cc/quick-bench
(trie/lookup reitit-tree "/v1/orgs/1/topics" {}))
;; 0.9µs (initial)
;; 0.5µs (protocols)
;; 1.0µs (with path paraµs)
;; 1.0µs (Match records)
;; 0.63µs (Single sweep path paraµs)
;; 0.51µs (Cleanup)
(cc/quick-bench (cc/quick-bench
(dotimes [_ 1000] (segment/lookup reitit-segment "/v1/orgs/1/topics")))
(segment/lookup reitit-segment "/v1/orgs/1/topics"))))
(comment (comment
(bench!)) (bench!))