Fast-router if only 1 static path.

* 200x faster on basic benchmark than the slowest one
This commit is contained in:
Tommi Reiman 2017-10-02 08:15:53 +03:00
parent 254908b70b
commit e9c0639914
3 changed files with 131 additions and 17 deletions

View file

@ -66,9 +66,48 @@
["/auth/recovery/token/:token" :auth/recovery] ["/auth/recovery/token/:token" :auth/recovery]
["/workspace/:project/:page" :workspace/page]])) ["/workspace/:project/:page" :workspace/page]]))
(defn routing-test [] (defn routing-test1 []
(suite "simple routing") (suite "static route")
;; 2.2µs
(title "bidi")
(let [call #(bidi/match-route bidi-routes "/auth/login")]
(assert (call))
(cc/quick-bench
(call)))
;; 1.5µs (-40%)
(title "ataraxy")
(let [call #(ataraxy/matches ataraxy-routes {:uri "/auth/login"})]
(assert (call))
(cc/quick-bench
(call)))
;; 1.1µs (-50%)
(title "pedestal - map-tree => prefix-tree")
(let [call #(pedestal/find-route pedestal-router {:path-info "/auth/login" :request-method :get})]
(assert (call))
(cc/quick-bench
(call)))
;; 1.5µs (-40%)
(title "compojure-api")
(let [call #(compojure-api-routes {:uri "/auth/login", :request-method :get})]
(assert (call))
(cc/quick-bench
(call)))
;; 11.5ns (-99,5%)
(title "reitit")
(let [call #(reitit/match-by-path reitit-routes "/auth/login")]
(assert (call))
(cc/quick-bench
(call))))
(defn routing-test2 []
(suite "wildcard route")
;; 15.4µs ;; 15.4µs
(title "bidi") (title "bidi")
@ -141,5 +180,6 @@
(call)))) (call))))
(comment (comment
(routing-test) (routing-test1)
(routing-test2)
(reverse-routing-test)) (reverse-routing-test))

View file

@ -213,17 +213,52 @@
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match params))))))) (match params)))))))
(defn fast-router
"Creates a super-fast router of 1 static route(s) and optional
expanded options. See [[router]] for available options"
([routes]
(fast-router routes {}))
([routes opts]
(when (or (not= (count routes) 1) (some impl/wild-route? routes))
(throw
(ex-info
(str ":fast-router requires exactly 1 static route: " routes)
{:routes routes})))
(let [[n :as names] (find-names routes opts)
[[p meta result] :as compiled] (compile-routes routes opts)
p #?(:clj (.intern ^String p) :cljs p)
match (->Match p meta result {} p)]
(reify Router
(router-name [_]
:fast-router)
(routes [_]
compiled)
(options [_]
opts)
(route-names [_]
names)
(match-by-path [_ path]
(if (#?(:clj .equals :cljs =) p path)
match))
(match-by-name [_ name]
(if (= n name)
match))
(match-by-name [_ name params]
(if (= n name)
(impl/fast-assoc match :params params)))))))
(defn mixed-router (defn mixed-router
"Creates two routers: [[lookup-router]] for static routes and "Creates two routers: [[lookup-router]] or [[fast-ruoter]] for
[[linear-router]] for wildcard routes. All routes should be static routes and [[linear-router]] for wildcard routes. All
non-conflicting. Takes resolved routes and optional routes should be non-conflicting. Takes resolved routes and optional
expanded options. See [[router]] for options." expanded options. See [[router]] for options."
([routes] ([routes]
(mixed-router routes {})) (mixed-router routes {}))
([routes opts] ([routes opts]
(let [{linear true, lookup false} (group-by impl/wild-route? routes) (let [{linear true, lookup false} (group-by impl/wild-route? routes)
linear-router (linear-router linear opts) ->static-router (if (= 1 (count lookup)) fast-router lookup-router)
lookup-router (lookup-router lookup opts) wildcard-router (linear-router linear opts)
static-router (->static-router lookup opts)
names (find-names routes opts)] names (find-names routes opts)]
(reify Router (reify Router
(router-name [_] (router-name [_]
@ -235,19 +270,19 @@
(route-names [_] (route-names [_]
names) names)
(match-by-path [_ path] (match-by-path [_ path]
(or (match-by-path lookup-router path) (or (match-by-path static-router path)
(match-by-path linear-router path))) (match-by-path wildcard-router path)))
(match-by-name [_ name] (match-by-name [_ name]
(or (match-by-name lookup-router name) (or (match-by-name static-router name)
(match-by-name linear-router name))) (match-by-name wildcard-router name)))
(match-by-name [_ name params] (match-by-name [_ name params]
(or (match-by-name lookup-router name params) (or (match-by-name static-router name params)
(match-by-name linear-router name params))))))) (match-by-name wildcard-router name params)))))))
(defn router (defn router
"Create a [[Router]] from raw route data and optionally an options map. "Create a [[Router]] from raw route data and optionally an options map.
If routes contain wildcards, a [[LinearRouter]] is used, otherwise a Selects implementation based on route details. The following options
[[LookupRouter]]. The following options are available: are available:
| key | description | | key | description |
| -------------|-------------| | -------------|-------------|
@ -265,10 +300,11 @@
(let [{:keys [router] :as opts} (meta-merge default-router-options opts) (let [{:keys [router] :as opts} (meta-merge default-router-options opts)
routes (resolve-routes data opts) routes (resolve-routes data opts)
conflicting (conflicting-routes routes) conflicting (conflicting-routes routes)
wilds? (some impl/wild-route? routes) wilds? (boolean (some impl/wild-route? routes))
all-wilds? (every? impl/wild-route? routes) all-wilds? (every? impl/wild-route? routes)
router (cond router (cond
router router router router
(and (= 1 (count routes)) (not wilds?)) fast-router
(not wilds?) lookup-router (not wilds?) lookup-router
all-wilds? linear-router all-wilds? linear-router
(not conflicting) mixed-router (not conflicting) mixed-router

View file

@ -70,6 +70,44 @@
(r/resolve-routes (r/resolve-routes
["/api/:version/ping"] {}))))))) ["/api/:version/ping"] {})))))))
(testing "fast-router"
(let [router (r/router ["/api" ["/ipa" ["/large" ::beer]]])]
(is (= :fast-router (r/router-name router)))
(is (= [["/api/ipa/large" {:name ::beer} nil]]
(r/routes router)))
(is (= true (map? (r/options router))))
(is (= (r/map->Match
{:template "/api/ipa/large"
:meta {:name ::beer}
:path "/api/ipa/large"
:params {}})
(r/match-by-path router "/api/ipa/large")))
(is (= (r/map->Match
{:template "/api/ipa/large"
:meta {:name ::beer}
:path "/api/ipa/large"
:params {:size "large"}})
(r/match-by-name router ::beer {:size "large"})))
(is (= nil (r/match-by-name router "ILLEGAL")))
(is (= [::beer] (r/route-names router)))
(testing "can't be created with wildcard routes"
(is (thrown-with-msg?
ExceptionInfo
#":fast-router requires exactly 1 static route"
(r/fast-router
(r/resolve-routes
["/api/:version/ping"] {})))))
(testing "can't be created with multiple routes"
(is (thrown-with-msg?
ExceptionInfo
#":fast-router requires exactly 1 static route"
(r/fast-router
(r/resolve-routes
[["/ping"]
["/pong"]] {})))))))
(testing "route coercion & compilation" (testing "route coercion & compilation"
(testing "custom compile" (testing "custom compile"