diff --git a/doc/images/opensensors.png b/doc/images/opensensors.png index d59d6dab..94bf228a 100644 Binary files a/doc/images/opensensors.png and b/doc/images/opensensors.png differ diff --git a/doc/performance.md b/doc/performance.md index a1f73585..466f0b39 100644 --- a/doc/performance.md +++ b/doc/performance.md @@ -1,6 +1,6 @@ # Performance -Reitit tries to be both great in features and be really, really fast. Originally the routing was ported from [Pedestal](http://pedestal.io/), but has been mostly rewritten. +Besides having great features, the goal of reitit is to be really, really fast. The routing was originally exported from Pedestal, but since rewritten. ![Opensensors perf test](images/opensensors.png) @@ -63,13 +63,13 @@ The routing sample taken from [bide](https://github.com/funcool/bide) README: (dotimes [_ 1000] (r/match-by-path routes "/auth/login"))) -;; Execution time mean (per 1000): 530 µs -> 1.9M ops/sec +;; Execution time mean (per 1000): 315 µs -> 3.2M ops/sec (cc/quick-bench (dotimes [_ 1000] (r/match-by-path routes "/workspace/1/1"))) ``` -Based on the [perf tests](https://github.com/metosin/reitit/tree/master/perf-test/clj/reitit/perf/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 4-24x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal). +Based on the [perf tests](https://github.com/metosin/reitit/tree/master/perf-test/clj/reitit/perf/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 6-40x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal). But, the example is too simple for any real benchmark. Also, some of the libraries always match on the `:request-method` too and by doing so, do more work than just match by path. Compojure does most work also by invoking the handler. @@ -79,7 +79,7 @@ So, we need to test something more realistic. To get better view on the real life routing performance, there is [test](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/opensensors_perf_test.clj) of a mid-size rest(ish) http api with 50+ routes, having a lot of path parameters. The route definitions are pulled off from the [OpenSensors](https://opensensors.io/) swagger definitions. -Thanks to the snappy new [segment-tree](https://github.com/metosin/reitit/blob/master/modules/reitit-core/src/reitit/segment.cljc) algorithm, `reitit-ring` is fastest here. Pedestal is also fast with it's [prefix-tree](https://en.wikipedia.org/wiki/Radix_tree) implementation. +Thanks to the snappy [SegmentTrie](https://github.com/metosin/reitit/blob/master/modules/reitit-core/java-src/reitit/SegmentTrie.java) (a modification of [Radix tree](https://en.wikipedia.org/wiki/Radix_tree)), `reitit-ring` is fastest here. [Calfpath](https://github.com/kumarshantanu/calfpath) and [Pedestal](https://github.com/pedestal/pedestal) are also quite fast. ![Opensensors perf](images/opensensors.png) @@ -99,13 +99,14 @@ The reitit routing perf is measured to get an internal baseline to optimize agai ### Looking out of the box -A quick poke to [routers in Go](https://github.com/julienschmidt/go-http-routing-benchmark) indicates that the reitit is only few times slower than the fastest routers in Go. Which is really awesome (if true). +A quick poke to [routers in Go](https://github.com/julienschmidt/go-http-routing-benchmark) indicates that the reitit is only few times slower than the fastest routers in Go. Which is kinda awesome. ### Performance tips Few things that have an effect on performance: * Wildcard-routes are an order of magnitude slower than static routes -* It's ok to mix non-wildcard and wildcard routes in a same routing tree as long as you don't disable the [conflict resolution](basics/route_conflicts.md) => if no conflicting routes are found, a `:mixed-router` can be created, which internally has a fast static path router and a separate wildcard-router. So, the static paths are still fast. +* Conflicting routes are served with LinearRouter, which is the slowest implementation. +* It's ok to mix non-wildcard, wildcard or even conflicting routes in a same routing tree. Reitit will create an hierarchy of routers to serve all the routes with best possible implementation. * Move computation from request processing time into creation time, using by compiling [middleware](ring/compiling_middleware.md) & [route data](advanced/configuring_routers.md). * Unmounted middleware (or interceptor) is infinitely faster than a mounted one effectively doing nothing. diff --git a/perf-test/clj/reitit/opensensors_perf_test.clj b/perf-test/clj/reitit/opensensors_perf_test.clj index eb6bc094..ba8c3813 100644 --- a/perf-test/clj/reitit/opensensors_perf_test.clj +++ b/perf-test/clj/reitit/opensensors_perf_test.clj @@ -9,6 +9,7 @@ [ataraxy.core :as ataraxy] [compojure.core :refer [routes context ANY]] [calfpath.core :as cp] + [calfpath.route :as cr] [io.pedestal.http.route.definition.table :as table] [io.pedestal.http.route.map-tree :as map-tree] @@ -367,7 +368,7 @@ ["/v1/users/:user-id/bookmarks" :get handler :route-name :test/route56] ["/v1/orgs/:org-id/topics" :get handler :route-name :test/route57]]))) -(defn opensensors-calfpath-handler [request] +(defn opensensors-calfpath-macro-handler [request] (cp/->uri request "/v2/whoami" [] (cp/->get request (handler request) nil) @@ -429,6 +430,68 @@ "/v1/orgs/:org-id/topics" [] (cp/->get request (handler request) nil) nil)) +(def opensensors-calfpath-routes + [{:uri "/v2/whoami" :nested [{:method :get, :handler handler}]} + {:uri "/v2/users/:user-id/datasets" :nested [{:method :get, :handler handler}]} + {:uri "/v2/public/projects/:project-id/datasets" :nested [{:method :get, :handler handler}]} + {:uri "/v1/public/topics/:topic" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/orgs/:org-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/search/topics/:term" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/invitations" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/devices/:batch/:type" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/topics" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/bookmarks/followers" :nested [{:method :get, :handler handler}]} + {:uri "/v2/datasets/:dataset-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/usage-stats" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/devices/:client-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/messages/user/:user-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/devices" :nested [{:method :get, :handler handler}]} + {:uri "/v1/public/users/:user-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/errors" :nested [{:method :get, :handler handler}]} + {:uri "/v1/public/orgs/:org-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/invitations" :nested [{:method :get, :handler handler}]} + {:uri "/v2/public/messages/dataset/bulk" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/devices/bulk" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/device-errors" :nested [{:method :get, :handler handler}]} + {:uri "/v2/login" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/usage-stats" :nested [{:method :get, :handler handler}]} + {:uri "/v2/users/:user-id/devices" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/claim-device/:client-id" :nested [{:method :get, :handler handler}]} + {:uri "/v2/public/projects/:project-id" :nested [{:method :get, :handler handler}]} + {:uri "/v2/public/datasets/:dataset-id" :nested [{:method :get, :handler handler}]} + {:uri "/v2/users/:user-id/topics/bulk" :nested [{:method :get, :handler handler}]} + {:uri "/v1/messages/device/:client-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/owned-orgs" :nested [{:method :get, :handler handler}]} + {:uri "/v1/topics/:topic" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/bookmark/:topic" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/members/:user-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/devices/:client-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/devices" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/members" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/members/invitation-data/:user-id" :nested [{:method :get, :handler handler}]} + {:uri "/v2/orgs/:org-id/topics" :nested [{:method :get, :handler handler}]} + {:uri "/v1/whoami" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/api-key" :nested [{:method :get, :handler handler}]} + {:uri "/v2/schemas" :nested [{:method :get, :handler handler}]} + {:uri "/v2/users/:user-id/topics" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/confirm-membership/:token" :nested [{:method :get, :handler handler}]} + {:uri "/v2/topics/:topic" :nested [{:method :get, :handler handler}]} + {:uri "/v1/messages/topic/:topic" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/devices/:client-id/reset-password" :nested [{:method :get, :handler handler}]} + {:uri "/v2/topics" :nested [{:method :get, :handler handler}]} + {:uri "/v1/login" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/orgs" :nested [{:method :get, :handler handler}]} + {:uri "/v2/public/messages/dataset/:dataset-id" :nested [{:method :get, :handler handler}]} + {:uri "/v1/topics" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs" :nested [{:method :get, :handler handler}]} + {:uri "/v1/users/:user-id/bookmarks" :nested [{:method :get, :handler handler}]} + {:uri "/v1/orgs/:org-id/topics" :nested [{:method :get, :handler handler}]}]) + +(def opensensors-calfpath-data-handler + (partial cr/dispatch (cr/compile-routes opensensors-calfpath-routes {:show-uris-400? false}))) + (comment (pedestal/find-route (map-tree/router @@ -487,7 +550,8 @@ reitit-ring-f (ring/ring-handler (ring/router opensensors-routes)) reitit-ring-fast-f (ring/ring-handler (ring/router opensensors-routes) nil {:inject-router? false, :inject-match? false}) bidi-f #(bidi/match-route opensensors-bidi-routes (:uri %)) - calfpath-f opensensors-calfpath-handler + calfpath-macros-f opensensors-calfpath-macro-handler + calfpath-data-f opensensors-calfpath-data-handler ataraxy-f (partial ataraxy/matches opensensors-ataraxy-routes) compojure-f opensensors-compojure-routes pedestal-f (partial pedestal/find-route opensensors-pedestal-routes) @@ -513,11 +577,14 @@ ;; 385ns (java-segment-router, no injects) (b! "reitit-ring-fast" reitit-ring-fast-f) + ;; 2258ns + (b! "calfpath-data" calfpath-data-f) + ;; 2821ns (b! "pedestal" pedestal-f) - ;; 4364ns (macros) - (b! "calfpath" calfpath-f) + ;; 4364ns + (b! "calfpath-macros" calfpath-macros-f) ;; 11615ns (b! "compojure" compojure-f)