From 1b0cc0a100a3c6d19a36c1927bc3b64697700b25 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Mon, 27 Apr 2020 08:38:27 +0300 Subject: [PATCH] Faster path conflict resolution, O(n2) -> O(n) --- modules/reitit-core/src/reitit/impl.cljc | 17 ++++++++------- modules/reitit-core/src/reitit/trie.cljc | 21 ++++++++++--------- .../clj/reitit/router_creation_perf_test.clj | 2 ++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 3e58e48b..cc902295 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -74,14 +74,15 @@ coerce (into [] (keep #(coerce % opts))))) (defn path-conflicting-routes [routes opts] - (-> (into {} - (comp (map-indexed (fn [index route] - [route (into #{} - (filter #(trie/conflicting-paths? (first route) (first %) opts)) - (subvec routes (inc index)))])) - (filter (comp seq second))) - routes) - (not-empty))) + (let [parts-and-routes (mapv (fn [[s :as r]] [(trie/split-path s opts) r]) routes)] + (-> (into {} (comp (map-indexed (fn [index [p r]] + [r (reduce + (fn [acc [p' r']] + (if (trie/conflicting-parts? p p') + (conj acc r') acc)) + #{} (subvec parts-and-routes (inc index)))])) + (filter (comp seq second))) parts-and-routes) + (not-empty)))) (defn unresolved-conflicts [path-conflicting] (-> (into {} diff --git a/modules/reitit-core/src/reitit/trie.cljc b/modules/reitit-core/src/reitit/trie.cljc index c4622b53..bbd1aded 100644 --- a/modules/reitit-core/src/reitit/trie.cljc +++ b/modules/reitit-core/src/reitit/trie.cljc @@ -132,17 +132,18 @@ (concat [(subs x i)] xs) xs))) +(defn conflicting-parts? [parts1 parts2] + (let [[[s1 & ss1] [s2 & ss2]] (-slice-start parts1 parts2)] + (cond + (= s1 s2 nil) true + (or (nil? s1) (nil? s2)) false + (or (catch-all? s1) (catch-all? s2)) true + (or (wild? s1) (wild? s2)) (recur (-slice-end s1 ss1) (-slice-end s2 ss2)) + (not= s1 s2) false + :else (recur ss1 ss2)))) + (defn conflicting-paths? [path1 path2 opts] - (loop [parts1 (split-path path1 opts) - parts2 (split-path path2 opts)] - (let [[[s1 & ss1] [s2 & ss2]] (-slice-start parts1 parts2)] - (cond - (= s1 s2 nil) true - (or (nil? s1) (nil? s2)) false - (or (catch-all? s1) (catch-all? s2)) true - (or (wild? s1) (wild? s2)) (recur (-slice-end s1 ss1) (-slice-end s2 ss2)) - (not= s1 s2) false - :else (recur ss1 ss2))))) + (conflicting-parts? (split-path path1 opts) (split-path path2 opts))) ;; ;; Creating Tries diff --git a/perf-test/clj/reitit/router_creation_perf_test.clj b/perf-test/clj/reitit/router_creation_perf_test.clj index 9349d128..4f812575 100644 --- a/perf-test/clj/reitit/router_creation_perf_test.clj +++ b/perf-test/clj/reitit/router_creation_perf_test.clj @@ -40,6 +40,7 @@ (suite "non-conflicting") ;; 104ms + ;; 11ms (reuse parts in conflict resolution) (bench! "default" (r/router hundred-routes)) ;; 7ms @@ -50,6 +51,7 @@ ;; 205ms ;; 105ms (cache path-conflicts) + ;; 13ms (reuse parts in conflict resolution) (bench! "default" (r/router routes {:conflicts nil})))) (comment