diff options
Diffstat (limited to 'bench/ruuter/bench.cljc')
| -rw-r--r-- | bench/ruuter/bench.cljc | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/bench/ruuter/bench.cljc b/bench/ruuter/bench.cljc new file mode 100644 index 0000000..0048d13 --- /dev/null +++ b/bench/ruuter/bench.cljc @@ -0,0 +1,134 @@ +(ns ruuter.bench + (:require [ruuter.core :as ruuter])) + +;; ── Benchmark Harness ────────────────────────────────────────────────── +;; Cross-platform (JVM, ClojureScript/Node.js, Babashka). + +(defn- now-ns + "Returns the current time in nanoseconds." + [] + #?(:cljs (* (.now js/performance) 1e6) + :clj (System/nanoTime))) + +(defn- bench + "Run `f` for `warmup-ms` to warm up, then run it for `measure-ms` and + return {:ops n, :elapsed-ms ms, :ops-per-sec ops/s, :ns-per-op ns}." + [f & {:keys [warmup-ms measure-ms] :or {warmup-ms 2000 measure-ms 5000}}] + ;; warmup + (let [warmup-end (+ (now-ns) (* warmup-ms 1e6))] + (while (< (now-ns) warmup-end) (f))) + ;; measure + (let [start (now-ns) + deadline (+ start (* measure-ms 1e6)) + total-ops (loop [cnt 0] + (f) + (let [cnt' (inc cnt)] + (if (< (now-ns) deadline) + (recur cnt') + cnt'))) + elapsed-ns (- (now-ns) start) + elapsed-ms (/ elapsed-ns 1e6) + ops-per-sec (long (/ (* total-ops 1e9) elapsed-ns)) + ns-per-op (long (/ elapsed-ns total-ops))] + {:ops total-ops + :elapsed-ms (long elapsed-ms) + :ops-per-sec ops-per-sec + :ns-per-op ns-per-op})) + +(defn- format-number + "Formats an integer with comma grouping, cross-platform." + [n] + #?(:cljs (let [s (str n) + reversed-digits (reverse s) + groups (map #(apply str (reverse %)) + (partition-all 3 reversed-digits))] + (string/join "," (reverse groups))) + :clj (format "%,d" n))) + +(defn- format-result [label {:keys [ops-per-sec ns-per-op]}] + (let [ops-str (format-number ops-per-sec) + ns-str (format-number ns-per-op)] + (str "| " label + (apply str (repeat (max 1 (- 46 (count label))) " ")) + "| " (apply str (repeat (max 0 (- 16 (count ops-str))) " ")) ops-str + " | " (apply str (repeat (max 0 (- 13 (count ns-str))) " ")) ns-str + " |"))) + +;; ── Route Definitions ────────────────────────────────────────────────── + +(def small-routes + "5 routes — typical small app." + [{:path "/" :method :get + :response {:status 200 :body "home"}} + {:path "/about" :method :get + :response {:status 200 :body "about"}} + {:path "/users/:id" :method :get + :response (fn [req] {:status 200 :body (str "user " (:id (:params req)))})} + {:path "/users/:id/posts/:post-id" :method :get + :response (fn [_req] {:status 200 :body "post"})} + {:path "/files/:path*" :method :get + :response (fn [_req] {:status 200 :body "file"})}]) + +(defn- generate-routes + "Generate `n` routes of the form /prefix-N/:id." + [n] + (vec + (concat + [{:path "/" :method :get :response {:status 200 :body "home"}}] + (for [i (range n)] + {:path (str "/section-" i "/:id") + :method :get + :response (fn [_req] {:status 200 :body (str "section " i)})}) + [{:path "/:catch-all*" :method :get + :response (fn [_req] {:status 200 :body "catch all"})}]))) + +(def medium-routes (generate-routes 50)) +(def large-routes (generate-routes 200)) + +;; ── Benchmark Scenarios ──────────────────────────────────────────────── + +(defn run-benchmarks [] + (println "\nRunning benchmarks (2s warmup + 5s measure each)...\n") + + (let [results + [["Small (5) — literal first" + (bench #(ruuter/route small-routes {:uri "/" :request-method :get}))] + ["Small (5) — literal middle" + (bench #(ruuter/route small-routes {:uri "/about" :request-method :get}))] + ["Small (5) — param match" + (bench #(ruuter/route small-routes {:uri "/users/42" :request-method :get}))] + ["Small (5) — nested params" + (bench #(ruuter/route small-routes {:uri "/users/42/posts/7" :request-method :get}))] + ["Small (5) — wildcard" + (bench #(ruuter/route small-routes {:uri "/files/a/b/c/d.txt" :request-method :get}))] + ["Small (5) — miss (404)" + (bench #(ruuter/route small-routes {:uri "/nope" :request-method :get}))] + ["Medium (52) — match first" + (bench #(ruuter/route medium-routes {:uri "/" :request-method :get}))] + ["Medium (52) — match middle" + (bench #(ruuter/route medium-routes {:uri "/section-25/42" :request-method :get}))] + ["Medium (52) — match last" + (bench #(ruuter/route medium-routes {:uri "/section-49/42" :request-method :get}))] + ["Medium (52) — catch-all wildcard" + (bench #(ruuter/route medium-routes {:uri "/unknown/path" :request-method :get}))] + ["Large (202) — match first" + (bench #(ruuter/route large-routes {:uri "/" :request-method :get}))] + ["Large (202) — match middle" + (bench #(ruuter/route large-routes {:uri "/section-100/42" :request-method :get}))] + ["Large (202) — match last" + (bench #(ruuter/route large-routes {:uri "/section-199/42" :request-method :get}))] + ["Large (202) — miss (404)" + (bench #(ruuter/route large-routes {:uri "/nothing" :request-method :post}))]]] + + (println "| Scenario | Ops/sec | ns/op |") + (println "|-----------------------------------------------|-----------------|--------------|") + (doseq [[label result] results] + (println (format-result label result))) + (println) + + results)) + +(defn -main [] + (run-benchmarks)) + +#?(:cljs (set! *main-cli-fn* -main)) |
