diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index a582badb..bcd54ac4 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -301,7 +301,7 @@ ([compiled-routes] (quarantine-router compiled-routes {})) ([compiled-routes opts] - (let [conflicting-paths (-> compiled-routes (impl/path-conflicting-routes opts) impl/conflicting-paths) + (let [conflicting-paths (impl/conflicting-paths (or (::path-conflicting opts) (impl/path-conflicting-routes compiled-routes opts))) conflicting? #(contains? conflicting-paths (first %)) {conflicting true, non-conflicting false} (group-by conflicting? compiled-routes) linear-router (linear-router conflicting opts) @@ -364,10 +364,10 @@ ([raw-routes] (router raw-routes {})) ([raw-routes opts] - (let [{:keys [router] :as opts} (merge (default-router-options) opts)] + (let [{:keys [router conflicts] :as opts} (merge (default-router-options) opts)] (try (let [routes (impl/resolve-routes raw-routes opts) - path-conflicting (impl/path-conflicting-routes routes opts) + path-conflicting (if-not (and router (not conflicts)) (impl/path-conflicting-routes routes opts)) name-conflicting (impl/name-conflicting-routes routes) compiled-routes (impl/compile-routes routes opts) wilds? (boolean (some (impl/->wild-route? opts) compiled-routes)) @@ -380,10 +380,8 @@ all-wilds? trie-router :else mixed-router)] - (when-let [conflicts (:conflicts opts)] - (when-let [conflict-report (impl/unresolved-conflicts - path-conflicting)] - (conflicts conflict-report))) + (when-let [conflict-report (and conflicts (impl/unresolved-conflicts path-conflicting))] + (conflicts conflict-report)) (when name-conflicting (exception/fail! :name-conflicts name-conflicting)) @@ -391,7 +389,7 @@ (when-let [validate (:validate opts)] (validate compiled-routes opts)) - (router compiled-routes opts)) + (router compiled-routes (assoc opts ::path-conflicting path-conflicting))) (catch #?(:clj Exception, :cljs js/Error) e (throw ((get opts :exception identity) e))))))) diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 2144d000..cc902295 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -71,17 +71,18 @@ (defn resolve-routes [raw-routes {:keys [coerce] :as opts}] (cond->> (->> (walk raw-routes opts) (map-data merge-data)) - coerce (into [] (keep #(coerce % opts))))) + 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 {} @@ -179,7 +180,7 @@ (URLDecoder/decode (if (.contains ^String s "+") (.replace ^String s "+" "%2B") - s) + ^String s) "UTF-8")) :cljs (js/decodeURIComponent s)))) 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 new file mode 100644 index 00000000..4f812575 --- /dev/null +++ b/perf-test/clj/reitit/router_creation_perf_test.clj @@ -0,0 +1,58 @@ +(ns reitit.router-creation-perf-test + (:require [reitit.perf-utils :refer [bench! suite]] + [reitit.core :as r] + [clojure.string :as str]) + (:import (java.util Random))) + +;; +;; start repl with `lein perf repl` +;; perf measured with the following setup: +;; +;; Model Name: MacBook Pro +;; Model Identifier: MacBookPro113 +;; Processor Name: Intel Core i7 +;; Processor Speed: 2,5 GHz +;; Number of Processors: 1 +;; Total Number of Cores: 4 +;; L2 Cache (per Core): 256 KB +;; L3 Cache: 6 MB +;; Memory: 16 GB +;; + +(defn random [^long seed] + (Random. seed)) + +(defn rand-str [^Random rnd len] + (apply str (take len (repeatedly #(char (+ (.nextInt rnd 26) 97)))))) + +(defn route [rnd] + (str/join "/" (repeatedly (+ 2 (.nextInt rnd 8)) (fn [] (rand-str rnd (.nextInt rnd 10)))))) + +(def hundred-routes + (let [rnd (random 1)] + (mapv (fn [n] [(route rnd) (keyword (str "route" n))]) (range 100)))) + +(conj hundred-routes (last hundred-routes)) + + +(defn bench-routers [] + + (suite "non-conflicting") + + ;; 104ms + ;; 11ms (reuse parts in conflict resolution) + (bench! "default" (r/router hundred-routes)) + + ;; 7ms + (bench! "linear" (r/router hundred-routes {:router r/linear-router, :conflicts nil})) + + (suite "conflicting") + (let [routes (conj hundred-routes [(first (last hundred-routes)) ::route])] + + ;; 205ms + ;; 105ms (cache path-conflicts) + ;; 13ms (reuse parts in conflict resolution) + (bench! "default" (r/router routes {:conflicts nil})))) + +(comment + (bench-routers))