You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/blog/tanstack-router-route-matching-tree-rewrite.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,7 +13,7 @@ We achieved a 20,000× performance improvement in route matching in TanStack Rou
13
13
14
14
One big responsibility of a router is to match a given URL pathname (e.g., `/users/123`) to a route definition (e.g., `/users/$userId`). This is deceptively complex when you consider all the different types of route segments (static, dynamic, optional, wildcard) and the priority rules that govern which route should match first.
15
15
16
-
Our previous route matching algorithm would look through every route in the route tree, and through a mix of pattern matching, manual look-aheads, and recursion, find the best match. As we added more features like optional segments and wildcards, the algorithm became increasingly complex and slow, and we started receiving reports of incorrect matches.
16
+
Our previous route matching algorithm was based on a sorted flat list of all routes, iterating through each to find a match. As we added more features like optional segments and wildcards, the algorithm became increasingly complex and slow, and we started receiving reports of incorrect matches. Our sorting did not adhere to a strict weak ordering, the sorting logic was not well-defined and even behaved differently between Chrome and Firefox.
17
17
18
18
We opted for a complete rewrite.
19
19
@@ -60,20 +60,20 @@ To match `/users/123`, we:
60
60
61
61
## Algorithmic Complexity
62
62
63
-
The reason we can get such a massive performance boost is because we've changed which variable drives the complexity of the algorithm. The bigger the route tree, the bigger the performance gain.
63
+
The reason we can get such a massive performance boost is because we changed which variable drives the complexity of the algorithm. The bigger the route tree, the bigger the performance gain.
64
64
65
65
- Old approach: `O(N)` where `N` is the number of routes in the tree.
66
66
- New approach: `O(M)` where `M` is the number of segments in the pathname.
67
67
68
-
(This is simplified, in practice it's more like `O(N * M)` vs. `O(M * log(N))` in the average case, but the point is that we've changed which variable dominates the complexity.)
68
+
(This is simplified, in practice it's more like `O(N * M)` vs. `O(M * log(N))` in the average case, but the point is that we changed which variable dominates the complexity.)
69
69
70
70
Using this new tree structure, each check eliminates a large number of possible routes, allowing us to quickly zero in on the correct match.
71
71
72
72
For example, imagine we have a route tree with 450 routes (fairly large app) and the tree can only eliminate 50% of routes at each segment check (this is unusually low, it's often much higher). With this bad setup, we have found a match in 9 checks (`2**9 > 450`). By contrast, the old approach _could_ have found the match on the first check, but in the worst case it would have had to check all 450 routes, which yields an average of 225 checks. Even in this simplified case, we are looking at a 25× performance improvement.
73
73
74
74
This is what makes tree structures so powerful.
75
75
76
-
In practice, we've observed:
76
+
In practice, we have observed:
77
77
78
78
- Small apps (10 routes): 60× faster
79
79
- Big apps (450 routes): 10,000× faster
@@ -197,7 +197,7 @@ function match(pathname: string): MatchResult {
However, aswe've seen before, some apps can have a very large number of unique routes, which means even more unique pathnames (e.g., route `/user/$id` is matched by `/user/1`, `/user/2`, etc). To prevent unbounded memory growth, we implement a Least Recently Used (LRU) cache. When the cache reaches a certain size, it automatically evicts the least recently used entries.
@@ -207,7 +207,7 @@ This data structure performs about half as well as a regular `Object` for writes
207
207
208
208
## Thefullstory
209
209
210
-
Thenumberswe've presented so far are impressive. They'realsocherry-pickedfromthebiggestappswetested, whichisbiasedinfavorofthenewalgorithm. Andthey're comparisons against the old, uncached algorithm. In reality, we'veaddedcachingawhileago. Wecanseethefullprogressionoverthelast4months:
@@ -227,4 +227,4 @@ While we are very happy with these results (and are probably done optimizing rou
227
227
228
228
---
229
229
230
-
Thiswasn't a "let'smakeitfaster" project, it was a "let's make it correct" project that happened to yield massive performance improvements as a side effect. I rarely see numbers this large in real benchmarks, so I hope you’ll forgive a bit of cherry-picking in this post.
230
+
Thiswasn't a "let'smakeitfaster" project, it was a "let's make it correct" project that happened to yield massive performance improvements as a side effect. We rarely see numbers this large in real benchmarks, so we hope you’ll forgive a bit of cherry-picking in this post.
0 commit comments