mirror of
https://github.com/metosin/reitit.git
synced 2025-12-16 16:01:11 +00:00
create empty path-parameters at creation time, 266ns->251ns (-6%)
This commit is contained in:
parent
950fef88d2
commit
9422cd28c1
8 changed files with 80 additions and 65 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
|
|
@ -40,9 +40,14 @@ public class Trie {
|
|||
}
|
||||
|
||||
public static class Match {
|
||||
public IPersistentMap params = PersistentArrayMap.EMPTY;
|
||||
public IPersistentMap params;
|
||||
public Object data;
|
||||
|
||||
public Match(IPersistentMap params, Object data) {
|
||||
this.params = params;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Map<Object, Object> m = new HashMap<>();
|
||||
|
|
@ -53,7 +58,7 @@ public class Trie {
|
|||
}
|
||||
|
||||
public interface Matcher {
|
||||
Match match(int i, int max, char[] path, Match match);
|
||||
Match match(int i, int max, char[] path);
|
||||
|
||||
int depth();
|
||||
|
||||
|
|
@ -76,7 +81,7 @@ public class Trie {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Match match(int i, int max, char[] path, Match match) {
|
||||
public Match match(int i, int max, char[] path) {
|
||||
if (max < i + size) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -85,7 +90,7 @@ public class Trie {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
return child.match(i + size, max, path, match);
|
||||
return child.match(i + size, max, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -104,22 +109,23 @@ public class Trie {
|
|||
}
|
||||
}
|
||||
|
||||
public static DataMatcher dataMatcher(Object data) {
|
||||
return new DataMatcher(data);
|
||||
public static DataMatcher dataMatcher(IPersistentMap params, Object data) {
|
||||
return new DataMatcher(params, data);
|
||||
}
|
||||
|
||||
static final class DataMatcher implements Matcher {
|
||||
private final IPersistentMap params;
|
||||
private final Object data;
|
||||
|
||||
DataMatcher(Object data) {
|
||||
DataMatcher(IPersistentMap params, Object data) {
|
||||
this.params = params;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Match match(int i, int max, char[] path, Match match) {
|
||||
public Match match(int i, int max, char[] path) {
|
||||
if (i == max) {
|
||||
match.data = data;
|
||||
return match;
|
||||
return new Match(params, data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -156,7 +162,7 @@ public class Trie {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Match match(int i, int max, char[] path, Match match) {
|
||||
public Match match(int i, int max, char[] path) {
|
||||
if (i < max && path[i] != end) {
|
||||
int stop = max;
|
||||
for (int j = i; j < max; j++) {
|
||||
|
|
@ -166,7 +172,7 @@ public class Trie {
|
|||
break;
|
||||
}
|
||||
}
|
||||
final Match m = child.match(stop, max, path, match);
|
||||
final Match m = child.match(stop, max, path);
|
||||
if (m != null) {
|
||||
m.params = m.params.assoc(key, decode(path, i, stop));
|
||||
}
|
||||
|
|
@ -191,25 +197,25 @@ public class Trie {
|
|||
}
|
||||
}
|
||||
|
||||
public static CatchAllMatcher catchAllMatcher(Keyword parameter, Object data) {
|
||||
return new CatchAllMatcher(parameter, data);
|
||||
public static CatchAllMatcher catchAllMatcher(Keyword parameter, IPersistentMap params, Object data) {
|
||||
return new CatchAllMatcher(parameter, params, data);
|
||||
}
|
||||
|
||||
static final class CatchAllMatcher implements Matcher {
|
||||
private final Keyword parameter;
|
||||
private final IPersistentMap params;
|
||||
private final Object data;
|
||||
|
||||
CatchAllMatcher(Keyword parameter, Object data) {
|
||||
CatchAllMatcher(Keyword parameter, IPersistentMap params, Object data) {
|
||||
this.parameter = parameter;
|
||||
this.params = params;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Match match(int i, int max, char[] path, Match match) {
|
||||
public Match match(int i, int max, char[] path) {
|
||||
if (i < max) {
|
||||
match.params = match.params.assoc(parameter, decode(path, i, max));
|
||||
match.data = data;
|
||||
return match;
|
||||
return new Match(params.assoc(parameter, decode(path, i, max)), data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -226,7 +232,7 @@ public class Trie {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + parameter + " " + new DataMatcher(data) + "]";
|
||||
return "[" + parameter + " " + new DataMatcher(null, data) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,9 +252,9 @@ public class Trie {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Match match(int i, int max, char[] path, Match match) {
|
||||
public Match match(int i, int max, char[] path) {
|
||||
for (int j = 0; j < size; j++) {
|
||||
final Match m = childs[j].match(i, max, path, match);
|
||||
final Match m = childs[j].match(i, max, path);
|
||||
if (m != null) {
|
||||
return m;
|
||||
}
|
||||
|
|
@ -273,7 +279,7 @@ public class Trie {
|
|||
}
|
||||
|
||||
public static Object lookup(Matcher matcher, String path) {
|
||||
return matcher.match(0, path.length(), path.toCharArray(), new Match());
|
||||
return matcher.match(0, path.length(), path.toCharArray());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
|
@ -283,8 +289,8 @@ public class Trie {
|
|||
staticMatcher("/auth/",
|
||||
linearMatcher(
|
||||
Arrays.asList(
|
||||
staticMatcher("login", dataMatcher(1)),
|
||||
staticMatcher("recovery", dataMatcher(2)))))));
|
||||
staticMatcher("login", dataMatcher(null, 1)),
|
||||
staticMatcher("recovery", dataMatcher(null, 2)))))));
|
||||
System.err.println(matcher);
|
||||
System.out.println(lookup(matcher, "/auth/login"));
|
||||
System.out.println(lookup(matcher, "/auth/recovery"));
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@
|
|||
(match-by-path [_ path]
|
||||
(if-let [match (trie/lookup matcher path)]
|
||||
(-> (:data match)
|
||||
(assoc :path-params (:path-params match))
|
||||
(assoc :path-params (:params match))
|
||||
(assoc :path path))))
|
||||
(match-by-name [_ name]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
|
|
@ -220,7 +220,7 @@
|
|||
(match-by-path [_ path]
|
||||
(if-let [match (trie/lookup pl path)]
|
||||
(-> (:data match)
|
||||
(assoc :path-params (:path-params match))
|
||||
(assoc :path-params (:params match))
|
||||
(assoc :path path))))
|
||||
(match-by-name [_ name]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
(defrecord Wild [value])
|
||||
(defrecord CatchAll [value])
|
||||
(defrecord Match [data path-params])
|
||||
(defrecord Node [children wilds catch-all data])
|
||||
(defrecord Match [params data])
|
||||
(defrecord Node [children wilds catch-all params data])
|
||||
|
||||
(defn wild? [x] (instance? Wild x))
|
||||
(defn catch-all? [x] (instance? CatchAll x))
|
||||
|
|
@ -19,9 +19,9 @@
|
|||
(depth [this])
|
||||
(length [this]))
|
||||
|
||||
(defn assoc-path-param [match k v]
|
||||
(let [params (:path-params match)]
|
||||
(assoc match :path-params (assoc params k v))))
|
||||
(defn assoc-param [match k v]
|
||||
(let [params (:params match)]
|
||||
(assoc match :params (assoc params k v))))
|
||||
|
||||
;; https://stackoverflow.com/questions/8033655/find-longest-common-prefix
|
||||
(defn common-prefix [s1 s2]
|
||||
|
|
@ -121,25 +121,25 @@
|
|||
;;
|
||||
|
||||
(defn- -node [m]
|
||||
(map->Node (merge {:children {}, :wilds {}, :catch-all {}} m)))
|
||||
(map->Node (merge {:children {}, :wilds {}, :catch-all {}, :params {}} m)))
|
||||
|
||||
(defn- -insert [node [path & ps] data]
|
||||
(defn- -insert [node [path & ps] params data]
|
||||
(let [node' (cond
|
||||
|
||||
(nil? path)
|
||||
(assoc node :data data)
|
||||
(assoc node :data data :params params)
|
||||
|
||||
(instance? Wild path)
|
||||
(let [next (first ps)]
|
||||
(if (or (instance? Wild next) (instance? CatchAll next))
|
||||
(ex/fail! (str "Two following wilds: " path ", " next))
|
||||
(update-in node [:wilds path] (fn [n] (-insert (or n (-node {})) ps data)))))
|
||||
(update-in node [:wilds path] (fn [n] (-insert (or n (-node {})) ps params data)))))
|
||||
|
||||
(instance? CatchAll path)
|
||||
(assoc-in node [:catch-all path] (-node {:data data}))
|
||||
(assoc-in node [:catch-all path] (-node {:params params, :data data}))
|
||||
|
||||
(str/blank? path)
|
||||
(-insert node ps data)
|
||||
(-insert node ps params data)
|
||||
|
||||
:else
|
||||
(or
|
||||
|
|
@ -148,20 +148,20 @@
|
|||
(if-let [cp (common-prefix p path)]
|
||||
(if (= cp p)
|
||||
;; insert into child node
|
||||
(let [n' (-insert n (conj ps (subs path (count p))) data)]
|
||||
(let [n' (-insert n (conj ps (subs path (count p))) params data)]
|
||||
(reduced (assoc-in node [:children p] n')))
|
||||
;; split child node
|
||||
(let [rp (subs p (count cp))
|
||||
rp' (subs path (count cp))
|
||||
n' (-insert (-node {}) ps data)
|
||||
n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil)]
|
||||
n' (-insert (-node {}) ps params data)
|
||||
n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil)]
|
||||
(reduced (update node :children (fn [children]
|
||||
(-> children
|
||||
(dissoc p)
|
||||
(assoc cp n'')))))))))
|
||||
nil (:children node))
|
||||
;; new child node
|
||||
(assoc-in node [:children path] (-insert (-node {}) ps data))))]
|
||||
(assoc-in node [:children path] (-insert (-node {}) ps params data))))]
|
||||
(if-let [child (get-in node' [:children ""])]
|
||||
;; optimize by removing empty paths
|
||||
(-> (merge-with merge (dissoc node' :data) child)
|
||||
|
|
@ -173,9 +173,9 @@
|
|||
(let [param (subs path start end)]
|
||||
(if percent? (js/decodeURIComponent param) param))))
|
||||
|
||||
(defn data-matcher [data]
|
||||
#?(:clj (Trie/dataMatcher data)
|
||||
:cljs (let [match (->Match data {})]
|
||||
(defn data-matcher [params data]
|
||||
#?(:clj (Trie/dataMatcher params data)
|
||||
:cljs (let [match (->Match params data)]
|
||||
(reify Matcher
|
||||
(match [_ i max _]
|
||||
(if (= i max)
|
||||
|
|
@ -207,23 +207,23 @@
|
|||
(loop [percent? false, j i]
|
||||
(if (= max j)
|
||||
(if-let [match (match matcher max max path)]
|
||||
(assoc-path-param match key (decode path i max percent?)))
|
||||
(assoc-param match key (decode path i max percent?)))
|
||||
(let [c ^char (get path j)]
|
||||
(condp = c
|
||||
end (if-let [match (match matcher j max path)]
|
||||
(assoc-path-param match key (decode path i j percent?)))
|
||||
(assoc-param match key (decode path i j percent?)))
|
||||
\% (recur true (inc j))
|
||||
(recur percent? (inc j))))))))
|
||||
(view [_] [key (view matcher)])
|
||||
(depth [_] (inc (depth matcher)))
|
||||
(length [_]))))
|
||||
|
||||
(defn catch-all-matcher [key data]
|
||||
#?(:clj (Trie/catchAllMatcher key data)
|
||||
:cljs (let [match (->Match data nil)]
|
||||
(defn catch-all-matcher [key params data]
|
||||
#?(:clj (Trie/catchAllMatcher key params data)
|
||||
:cljs (let [match (->Match params data)]
|
||||
(reify Matcher
|
||||
(match [_ i max path]
|
||||
(if (< i max) (assoc-path-param match key (decode path i max true))))
|
||||
(if (< i max) (assoc-param match key (decode path i max true))))
|
||||
(view [_] [key [data]])
|
||||
(depth [_] 1)
|
||||
(length [_])))))
|
||||
|
|
@ -255,12 +255,14 @@
|
|||
(insert acc p d))
|
||||
node routes))
|
||||
([node path data]
|
||||
(-insert (or node (-node {})) (split-path path) data)))
|
||||
(let [parts (split-path path)
|
||||
params (zipmap (->> parts (remove string?) (map :value)) (repeat nil))]
|
||||
(-insert (or node (-node {})) (split-path path) params data))))
|
||||
|
||||
(defn compile [{:keys [data children wilds catch-all]}]
|
||||
(defn compile [{:keys [data params children wilds catch-all] :or {params {}}}]
|
||||
(let [ends (fn [{:keys [children]}] (or (keys children) ["/"]))
|
||||
matchers (-> []
|
||||
(cond-> data (conj (data-matcher data)))
|
||||
(cond-> data (conj (data-matcher params data)))
|
||||
(into (for [[p c] children] (static-matcher p (compile c))))
|
||||
(into
|
||||
(for [[p c] wilds]
|
||||
|
|
@ -269,11 +271,11 @@
|
|||
(if (next ends)
|
||||
(ex/fail! (str "Trie compliation error: wild " p " has two terminators: " ends))
|
||||
(wild-matcher p (ffirst ends) (compile c))))))
|
||||
(into (for [[p c] catch-all] (catch-all-matcher (:value p) (:data c)))))]
|
||||
(into (for [[p c] catch-all] (catch-all-matcher (:value p) params (:data c)))))]
|
||||
(cond
|
||||
(> (count matchers) 1) (linear-matcher matchers)
|
||||
(= (count matchers) 1) (first matchers)
|
||||
:else (data-matcher nil))))
|
||||
:else (data-matcher {} nil))))
|
||||
|
||||
(defn pretty [matcher]
|
||||
#?(:clj (-> matcher str read-string eval)
|
||||
|
|
@ -281,9 +283,9 @@
|
|||
|
||||
(defn lookup [matcher path]
|
||||
#?(:clj (if-let [match ^Trie$Match (Trie/lookup ^Trie$Matcher matcher ^String path)]
|
||||
(->Match (.data match) (.params match)))
|
||||
(->Match (.params match) (.data match)))
|
||||
:cljs (if-let [match (match matcher 0 (count path) path)]
|
||||
(->Match (:data match) (:path-params match)))))
|
||||
(->Match (:params match) (:data match)))))
|
||||
|
||||
;;
|
||||
;; spike
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@
|
|||
;; 281ns (trie-router, no injects, optimized)
|
||||
;; 277ns (trie-router, no injects, switch-case) - 690ns clojure
|
||||
;; 273ns (trie-router, no injects, direct-data)
|
||||
;; 256ns (trie-router, pre-defined parameters)
|
||||
(let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})]
|
||||
(title "param")
|
||||
(assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req)))
|
||||
|
|
@ -347,8 +348,9 @@
|
|||
;; 66µs (trie-router, no injects)
|
||||
;; 64µs (trie-router, no injects, optimized) - 124µs (clojure)
|
||||
;; 63µs (trie-router, no injects, switch-case) - 124µs (clojure)
|
||||
;; 63ns (trie-router, no injects, direct-data)
|
||||
;; 54ns (trie-router, non-transient params)
|
||||
;; 63µs (trie-router, no injects, direct-data)
|
||||
;; 54µs (trie-router, non-transient params)
|
||||
;; 49µs (trie-router, pre-defined parameters)
|
||||
(let [requests (mapv route->req routes)]
|
||||
(title "all")
|
||||
(cc/quick-bench
|
||||
|
|
|
|||
|
|
@ -573,6 +573,7 @@
|
|||
;; 194ns (trie)
|
||||
;; 160ns (trie, prioritized)
|
||||
;; 130ns (trie, non-transient, direct-data)
|
||||
;; 121ns (trie, pre-defined parameters)
|
||||
(b! "reitit" reitit-f)
|
||||
|
||||
;; 2845ns
|
||||
|
|
@ -587,12 +588,14 @@
|
|||
;; 323ns (trie, prioritized)
|
||||
;; 289ns (trie, prioritized, zero-copy)
|
||||
;; 266ns (trie, non-transient, direct-data)
|
||||
;; 251ns (trie, pre-defined parameters)
|
||||
(b! "reitit-ring" reitit-ring-f)
|
||||
|
||||
;; 385ns (java-segment-router, no injects)
|
||||
;; 271ms (trie)
|
||||
;; 240ns (trie, prioritized)
|
||||
;; 214ns (trie, non-transient, direct-data)
|
||||
;; 187ns (trie, pre-defined parameters)
|
||||
(b! "reitit-ring-fast" reitit-ring-fast-f)
|
||||
|
||||
;; 2553ns (linear-router)
|
||||
|
|
@ -631,6 +634,7 @@
|
|||
;; 409ns (transient)
|
||||
;; 409ns (staticMultiMatcher)
|
||||
;; 305ns (non-persistent-params)
|
||||
;; 293ns (pre-defined parameters)
|
||||
(let [app (ring/ring-handler (ring/router opensensors-routes) {:inject-match? false, :inject-router? false})
|
||||
request {:uri "/v1/users/1/devices/1", :request-method :get}]
|
||||
(doseq [[p r] (-> app (ring/get-router) (r/routes))]
|
||||
|
|
@ -643,6 +647,7 @@
|
|||
; "Elapsed time: 9183.657012 msecs"
|
||||
; "Elapsed time: 8674.70132 msecs"
|
||||
; "Elapsed time: 6714.434915 msecs"
|
||||
; "Elapsed time: 6325.310043 msecs"
|
||||
(time
|
||||
(dotimes [_ 20000000]
|
||||
(app request)))
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
(println)
|
||||
(let [times (for [[path [mean lower]] (bench-routes routes req f)]
|
||||
(do
|
||||
(when verbose? (println (format "%7s %7s" mean lower) "\t" path))
|
||||
(when verbose? (println (format "%7s\t%7s" mean lower) "\t" path))
|
||||
[mean lower]))]
|
||||
(title (str "average, lower/mean: "
|
||||
(int (/ (reduce + (map second times)) (count times))) "/"
|
||||
|
|
|
|||
|
|
@ -14,24 +14,24 @@
|
|||
"/olipa/kerran/{*valvavan.suuri/avaruus}", "/olipa/kerran/{*valvavan.suuri/avaruus}"))
|
||||
|
||||
(deftest tests
|
||||
(is (= (trie/->Match {:a 1} {})
|
||||
(is (= (trie/->Match {} {:a 1})
|
||||
(-> (trie/insert nil "/foo" {:a 1})
|
||||
(trie/compile)
|
||||
(trie/lookup "/foo"))))
|
||||
|
||||
(is (= (trie/->Match {:a 1} {})
|
||||
(is (= (trie/->Match {} {:a 1})
|
||||
(-> (trie/insert nil "/foo" {:a 1})
|
||||
(trie/insert "/foo/*bar" {:b 1})
|
||||
(trie/compile)
|
||||
(trie/lookup "/foo"))))
|
||||
|
||||
(is (= (trie/->Match {:b 1} {:bar "bar"})
|
||||
(is (= (trie/->Match {:bar "bar"} {:b 1})
|
||||
(-> (trie/insert nil "/foo" {:a 1})
|
||||
(trie/insert "/foo/*bar" {:b 1})
|
||||
(trie/compile)
|
||||
(trie/lookup "/foo/bar"))))
|
||||
|
||||
(is (= (trie/->Match {:a 1} {})
|
||||
(is (= (trie/->Match {} {:a 1})
|
||||
(-> (trie/insert nil "" {:a 1})
|
||||
(trie/compile)
|
||||
(trie/lookup "")))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue