1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
# Ruuter Benchmark Results
Each benchmark: 2s warmup, 5s measurement window.
## Environment
- **Machine**: Macbook Pro (M4 Max 48GB)
- **JVM**: Java 25.0.2, Clojure 1.12.4
- **ClojureScript**: 1.12.134, Node.js v24.11.1
- **Babashka**: 1.12.212
- **Jank**: 0.1 (Apr 2026)
## Runtime Comparison (Current Versions)
Current versions:
- **JVM (Clojure)**: v2.1.1
- **ClojureScript (Node.js)**: v2.1.1
- **Babashka**: v2.1.1
- **Jank**: v2.1.1
| Scenario | Clojure ops/s | ClojureScript (Node.js) ops/s | Babashka ops/s | Jank ops/s |
|---|---:|---:|---:|---:|
| Small (5) - literal first | 5,751,517 | 826,109 | 561,707 | 632,139 |
| Small (5) - literal middle | 2,236,756 | 458,372 | 328,534 | 293,751 |
| Small (5) - param match | 1,138,681 | 244,455 | 194,415 | 147,771 |
| Small (5) - nested params | 725,794 | 159,997 | 136,953 | 95,436 |
| Small (5) - wildcard | 1,038,987 | 222,914 | 205,294 | 121,320 |
| Small (5) - miss (404) | 3,507,344 | 696,492 | 611,017 | 452,239 |
| Medium (52) - match first | 5,464,333 | 729,185 | 559,724 | 615,761 |
| Medium (52) - match middle | 947,366 | 198,538 | 167,396 | 124,459 |
| Medium (52) - match last | 943,909 | 199,165 | 168,330 | 126,711 |
| Medium (52) - catch-all wildcard | 1,611,263 | 339,559 | 274,306 | 191,428 |
| Large (202) - match first | 5,086,938 | 668,097 | 553,183 | 606,074 |
| Large (202) - match middle | 931,339 | 195,975 | 168,974 | 125,689 |
| Large (202) - match last | 925,180 | 195,752 | 168,347 | 124,528 |
| Large (202) - miss (404) | 2,278,714 | 443,050 | 458,547 | 338,429 |
## Analysis
- **JVM (Clojure)** leads in all scenarios in this benchmark set, peaking at ~5.8M ops/sec.
- **ClojureScript (Node.js)**, **Babashka**, and **Jank** are in the same general tier, with ClojureScript typically leading and Jank usually close behind.
- **Babashka** and **Jank** are very close on several large-route scenarios, with Babashka slightly ahead on large miss/404.
- Across runtimes, throughput stays relatively stable as route count grows, consistent with trie-based matching by path depth.
### Running benchmarks
```bash
# JVM
clojure -Sdeps '{:paths ["src" "bench"]}' -M -m ruuter.bench
# ClojureScript (Node.js)
clojure -Sdeps '{:paths ["src" "bench"] :deps {org.clojure/clojurescript {:mvn/version "1.10.879"}}}' \
-M -m cljs.main --target node --output-to bench-out/bench.js -c ruuter.bench
node bench-out/bench.js
# Babashka
bb -m ruuter.bench
# Jank
jank run --module-path src:bench jank_bench_runner.jank
```
### Route set sizes
- **Small (5)**: 5 routes — typical small app (literal, params, wildcard)
- **Medium (52)**: 52 routes — 1 literal + 50 parameterized + 1 catch-all wildcard
- **Large (202)**: 202 routes — 1 literal + 200 parameterized + 1 catch-all wildcard
|