From e9c0639914631511b478b1a838fa4ce7c301f3e7 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Mon, 2 Oct 2017 08:15:53 +0300 Subject: [PATCH 1/2] Fast-router if only 1 static path. * 200x faster on basic benchmark than the slowest one --- perf-test/clj/reitit/perf_test.clj | 46 +++++++++++++++++++-- src/reitit/core.cljc | 64 +++++++++++++++++++++++------- test/cljc/reitit/core_test.cljc | 38 ++++++++++++++++++ 3 files changed, 131 insertions(+), 17 deletions(-) diff --git a/perf-test/clj/reitit/perf_test.clj b/perf-test/clj/reitit/perf_test.clj index c4d1345e..3c08d1cf 100644 --- a/perf-test/clj/reitit/perf_test.clj +++ b/perf-test/clj/reitit/perf_test.clj @@ -66,9 +66,48 @@ ["/auth/recovery/token/:token" :auth/recovery] ["/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 (title "bidi") @@ -141,5 +180,6 @@ (call)))) (comment - (routing-test) + (routing-test1) + (routing-test2) (reverse-routing-test)) diff --git a/src/reitit/core.cljc b/src/reitit/core.cljc index 652abcca..672fb7f1 100644 --- a/src/reitit/core.cljc +++ b/src/reitit/core.cljc @@ -213,17 +213,52 @@ (if-let [match (impl/fast-get lookup name)] (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 - "Creates two routers: [[lookup-router]] for static routes and - [[linear-router]] for wildcard routes. All routes should be - non-conflicting. Takes resolved routes and optional + "Creates two routers: [[lookup-router]] or [[fast-ruoter]] for + static routes and [[linear-router]] for wildcard routes. All + routes should be non-conflicting. Takes resolved routes and optional expanded options. See [[router]] for options." ([routes] (mixed-router routes {})) ([routes opts] (let [{linear true, lookup false} (group-by impl/wild-route? routes) - linear-router (linear-router linear opts) - lookup-router (lookup-router lookup opts) + ->static-router (if (= 1 (count lookup)) fast-router lookup-router) + wildcard-router (linear-router linear opts) + static-router (->static-router lookup opts) names (find-names routes opts)] (reify Router (router-name [_] @@ -235,19 +270,19 @@ (route-names [_] names) (match-by-path [_ path] - (or (match-by-path lookup-router path) - (match-by-path linear-router path))) + (or (match-by-path static-router path) + (match-by-path wildcard-router path))) (match-by-name [_ name] - (or (match-by-name lookup-router name) - (match-by-name linear-router name))) + (or (match-by-name static-router name) + (match-by-name wildcard-router name))) (match-by-name [_ name params] - (or (match-by-name lookup-router name params) - (match-by-name linear-router name params))))))) + (or (match-by-name static-router name params) + (match-by-name wildcard-router name params))))))) (defn router "Create a [[Router]] from raw route data and optionally an options map. - If routes contain wildcards, a [[LinearRouter]] is used, otherwise a - [[LookupRouter]]. The following options are available: + Selects implementation based on route details. The following options + are available: | key | description | | -------------|-------------| @@ -265,10 +300,11 @@ (let [{:keys [router] :as opts} (meta-merge default-router-options opts) routes (resolve-routes data opts) conflicting (conflicting-routes routes) - wilds? (some impl/wild-route? routes) + wilds? (boolean (some impl/wild-route? routes)) all-wilds? (every? impl/wild-route? routes) router (cond router router + (and (= 1 (count routes)) (not wilds?)) fast-router (not wilds?) lookup-router all-wilds? linear-router (not conflicting) mixed-router diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index 6a50c46c..defae379 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -70,6 +70,44 @@ (r/resolve-routes ["/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 "custom compile" From 8afbbee8130dda07b86642c99ed3cc5d2ce31e1e Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Mon, 2 Oct 2017 17:53:07 +0300 Subject: [PATCH 2/2] fast-router -> single-static-path-router --- src/reitit/core.cljc | 16 ++++++++-------- test/cljc/reitit/core_test.cljc | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/reitit/core.cljc b/src/reitit/core.cljc index 672fb7f1..9b39df0c 100644 --- a/src/reitit/core.cljc +++ b/src/reitit/core.cljc @@ -213,16 +213,16 @@ (if-let [match (impl/fast-get lookup name)] (match params))))))) -(defn fast-router - "Creates a super-fast router of 1 static route(s) and optional +(defn single-static-path-router + "Creates a fast router of 1 static route(s) and optional expanded options. See [[router]] for available options" ([routes] - (fast-router routes {})) + (single-static-path-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) + (str ":single-static-path-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) @@ -230,7 +230,7 @@ match (->Match p meta result {} p)] (reify Router (router-name [_] - :fast-router) + :single-static-path-router) (routes [_] compiled) (options [_] @@ -248,7 +248,7 @@ (impl/fast-assoc match :params params))))))) (defn mixed-router - "Creates two routers: [[lookup-router]] or [[fast-ruoter]] for + "Creates two routers: [[lookup-router]] or [[single-static-path-router]] for static routes and [[linear-router]] for wildcard routes. All routes should be non-conflicting. Takes resolved routes and optional expanded options. See [[router]] for options." @@ -256,7 +256,7 @@ (mixed-router routes {})) ([routes opts] (let [{linear true, lookup false} (group-by impl/wild-route? routes) - ->static-router (if (= 1 (count lookup)) fast-router lookup-router) + ->static-router (if (= 1 (count lookup)) single-static-path-router lookup-router) wildcard-router (linear-router linear opts) static-router (->static-router lookup opts) names (find-names routes opts)] @@ -304,7 +304,7 @@ all-wilds? (every? impl/wild-route? routes) router (cond router router - (and (= 1 (count routes)) (not wilds?)) fast-router + (and (= 1 (count routes)) (not wilds?)) single-static-path-router (not wilds?) lookup-router all-wilds? linear-router (not conflicting) mixed-router diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index defae379..669b988c 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -70,9 +70,9 @@ (r/resolve-routes ["/api/:version/ping"] {}))))))) - (testing "fast-router" + (testing "single-static-path-router" (let [router (r/router ["/api" ["/ipa" ["/large" ::beer]]])] - (is (= :fast-router (r/router-name router))) + (is (= :single-static-path-router (r/router-name router))) (is (= [["/api/ipa/large" {:name ::beer} nil]] (r/routes router))) (is (= true (map? (r/options router)))) @@ -94,16 +94,16 @@ (testing "can't be created with wildcard routes" (is (thrown-with-msg? ExceptionInfo - #":fast-router requires exactly 1 static route" - (r/fast-router + #":single-static-path-router requires exactly 1 static route" + (r/single-static-path-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 + #":single-static-path-router requires exactly 1 static route" + (r/single-static-path-router (r/resolve-routes [["/ping"] ["/pong"]] {})))))))