linear-router is backed by a segment-router

This commit is contained in:
Tommi Reiman 2019-01-17 08:13:25 +02:00
parent 4e7963be91
commit 95ebdfa6a4
6 changed files with 40 additions and 50 deletions

View file

@ -4,7 +4,8 @@
### `reitit-core`
* `reitit.core/Expand` can be extended, fixes [#201](https://github.com/metosin/reitit/issues/201).
* new snappy Java-backed `SegmentTrie` routing algorithm and data structure, making wildcard routing ~2x faster on the JVM
* new snappy Java-backed `SegmentTrie` routing algorithm and data structure (for `reitit.core/segment-router`, making wildcard routing ~2x faster on the JVM
* `reitit.core/linear-router` uses the segment router behind the scenes, 2-4x faster on the JVM too.
### `reitit-ring`

View file

@ -297,6 +297,10 @@ public class SegmentTrie {
}
}
public static Matcher scanner(List<Matcher> matchers) {
return new LinearMatcher(matchers);
}
public static Match lookup(Matcher matcher, String path) {
return matcher.match(0, split(path), new Match());
}

View file

@ -128,7 +128,7 @@
(mapv (comp vec (partial take 2)) routes))
(defn route-info [route]
(select-keys (impl/create route) [:path :path-parts :path-params :result :data]))
(impl/create route))
(defprotocol Router
(router-name [this])
@ -177,11 +177,12 @@
f #(if-let [path (impl/path-for route %)]
(->Match p data result (impl/url-decode-coll %) path)
(->PartialMatch p data result % path-params))]
[(conj pl route)
[(conj pl (-> (segment/insert nil p (->Match p data result nil nil)) (segment/compile)))
(if name (assoc nl name f) nl)]))
[[] {}]
compiled-routes)
lookup (impl/fast-map nl)
scanner (segment/scanner pl)
routes (uncompile-routes compiled-routes)]
^{:type ::router}
(reify
@ -197,11 +198,10 @@
(route-names [_]
names)
(match-by-path [_ path]
(reduce
(fn [_ ^Route route]
(if-let [path-params ((:matcher route) path)]
(reduced (->Match (:path route) (:data route) (:result route) (impl/url-decode-coll path-params) path))))
nil pl))
(if-let [match (segment/lookup scanner path)]
(-> (:data match)
(assoc :path-params (:path-params match))
(assoc :path path))))
(match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)]
(match nil)))

View file

@ -66,18 +66,16 @@
(let [key (keyword token)]
(-> out
(update-in [:path-parts] conj key)
(update-in [:path-params] conj key)
(assoc-in [:path-constraints key] "([^/]+)"))))
(update-in [:path-params] conj key))))
#"^\*(.*)$" :>> (fn [[_ token]]
(let [key (keyword token)]
(-> out
(update-in [:path-parts] conj key)
(update-in [:path-params] conj key)
(assoc-in [:path-constraints key] "(.*)"))))
(update-in [:path-params] conj key))))
(update-in out [:path-parts] conj string)))
(defn- parse-path
([pattern] (parse-path {:path-parts [] :path-params [] :path-constraints {}} pattern))
([pattern] (parse-path {:path-parts [] :path-params #{}} pattern))
([accumulated-info pattern]
(if-let [m (re-matches #"/(.*)" pattern)]
(let [[_ path] m]
@ -86,45 +84,21 @@
(str/split path #"/")))
(throw (ex-info "Routes must start from the root, so they must begin with a '/'" {:pattern pattern})))))
;; TODO: is this correct?
(defn- re-quote [x]
#?(:clj (Pattern/quote x)
:cljs (str/replace x #"([.?*+^$[\\]\\\\(){}|-])" "\\$1")))
(defn- path-regex [{:keys [path-parts path-constraints] :as route}]
(let [[pp & pps] path-parts
path-parts (if (and (seq pps) (string? pp) (empty? pp)) pps path-parts)]
(re-pattern
(apply str
(interleave (repeat "/")
(map #(or (get path-constraints %) (re-quote %))
path-parts))))))
(defn- path-matcher [route]
(let [{:keys [path-re path-params]} route]
(fn [path]
(when-let [m (re-matches path-re path)]
(zipmap path-params (rest m))))))
;;
;; Routing (c) Metosin
;;
(defrecord Route [path matcher path-parts path-params data result])
(defrecord Route [path path-parts path-params data result])
(defn create [[path data result]]
(let [path #?(:clj (.intern ^String path) :cljs path)]
(as-> (parse-path path) $
(assoc $ :path-re (path-regex $))
(merge $ {:path path
:matcher (if (contains-wilds? path)
(path-matcher $)
#(if (#?(:clj .equals, :cljs =) path %) {}))
:result result
:data data})
(dissoc $ :path-re :path-constraints)
(update $ :path-params set)
(map->Route $))))
(let [path #?(:clj (.intern ^String path) :cljs path)
{:keys [path-parts path-params]} (parse-path path)]
(map->Route
{:path-params path-params
:path-parts path-parts
:path path
:result result
:data data})))
(defn wild-route? [[path]]
(contains-wilds? path))

View file

@ -59,6 +59,11 @@
#?(:cljs trie
:clj (.matcher ^SegmentTrie (or trie (SegmentTrie.)))))
(defn scanner [compiled-tries]
"Returns a new compiled trie that does linear scan on the given compiled tries on [[lookup]]."
#?(:cljs (fn [path] (some (fn [trie] (lookup trie path)) compiled-tries))
:clj (SegmentTrie/scanner compiled-tries)))
(defn lookup [trie path]
"Looks the path from a Segment Trie. Returns a [[Match]] or `nil`."
#?(:cljs (if-let [match (-lookup trie (impl/segments path) {})]

View file

@ -13,7 +13,8 @@
[io.pedestal.http.route.definition.table :as table]
[io.pedestal.http.route.map-tree :as map-tree]
[io.pedestal.http.route.router :as pedestal]))
[io.pedestal.http.route.router :as pedestal]
[reitit.core :as r]))
;;
;; start repl with `lein perf repl`
@ -551,6 +552,7 @@
router (reitit/router routes)
reitit-f #(reitit/match-by-path router (:uri %))
reitit-ring-f (ring/ring-handler (ring/router opensensors-routes))
reitit-ring-linear-f (ring/ring-handler (ring/router opensensors-routes {:router r/linear-router}))
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-macros-f opensensors-calfpath-macro-handler
@ -579,19 +581,23 @@
(b! "reitit-ring" reitit-ring-f)
;; 385ns (java-segment-router, no injects)
(b! "reitit-ring-fast" reitit-ring-fast-f)
#_(b! "reitit-ring-fast" reitit-ring-fast-f)
;; 2553ns (linear-router)
;; 630ns (segment-router-backed)
#_(b! "reitit-ring-linear" reitit-ring-linear-f)
;; 2137ns
(b! "calfpath-walker" calfpath-walker-f)
;; 4774ns
(b! "calfpath-unroll" calfpath-unroll-f)
#_(b! "calfpath-unroll" calfpath-unroll-f)
;; 2821ns
(b! "pedestal" pedestal-f)
;; 4803ns
(b! "calfpath-macros" calfpath-macros-f)
#_(b! "calfpath-macros" calfpath-macros-f)
;; 11615ns
(b! "compojure" compojure-f)