Skip to content

Commit

Permalink
Refactor priority logic.
Browse files Browse the repository at this point in the history
  • Loading branch information
CathalMullan committed Jan 5, 2025
1 parent 31ec191 commit 03b2360
Show file tree
Hide file tree
Showing 32 changed files with 1,037 additions and 426 deletions.
51 changes: 31 additions & 20 deletions BENCHMARKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,47 @@ Check out our [codspeed results](https://codspeed.io/DuskSystems/wayfind/benchma

For all benchmarks, we convert any extracted parameters to strings.

Some routers perform this operations automatically, while others require them to be done manually.
ALl routers provide a way to get parameters as strings, but some delay the actual decoding until post-search.

We do this to try and match behaviour as best as possible. This is as close to an "apples-to-apples" comparison as we can get.
We should also percent-decode too, to make our results even fairer.

## `matchit` inspired benches
| Library | Percent Decoding | String Parameters |
|:-----------------|:----------------:|:-----------------:|
| wayfind | no | yes |
| actix-router | partial | yes |
| matchit | no | delayed |
| ntex-router | partial | yes |
| path-tree | no | delayed |
| route-recognizer | no | yes |
| routefinder | no | yes |
| xitca-router | no | yes |

### `matchit` inspired benches

In a router of 130 templates, benchmark matching 4 paths.

| Library | Time | Alloc Count | Alloc Size | Dealloc Count | Dealloc Size |
|:-----------------|----------:|------------:|-----------:|--------------:|-------------:|
| wayfind | 329.54 ns | 5 | 329 B | 5 | 329 B |
| matchit | 372.73 ns | 5 | 480 B | 5 | 512 B |
| path-tree | 435.01 ns | 5 | 480 B | 5 | 512 B |
| xitca-router | 519.06 ns | 8 | 864 B | 8 | 896 B |
| ntex-router | 2.1569 µs | 19 | 1.312 KB | 19 | 1.344 KB |
| route-recognizer | 3.0280 µs | 161 | 8.569 KB | 161 | 8.601 KB |
| routefinder | 6.1353 µs | 68 | 5.088 KB | 68 | 5.12 KB |
| actix-router | 21.199 µs | 215 | 14 KB | 215 | 14.03 KB |
| wayfind | 289.04 ns | 4 | 288 B | 4 | 288 B |
| matchit | 381.78 ns | 4 | 448 B | 4 | 448 B |
| path-tree | 416.97 ns | 4 | 448 B | 4 | 448 B |
| xitca-router | 502.66 ns | 7 | 832 B | 7 | 832 B |
| ntex-router | 2.0693 µs | 18 | 1.28 KB | 18 | 1.28 KB |
| route-recognizer | 2.9028 µs | 160 | 8.537 KB | 160 | 8.537 KB |
| routefinder | 6.0791 µs | 67 | 5.056 KB | 67 | 5.056 KB |
| actix-router | 20.820 µs | 214 | 13.96 KB | 214 | 13.96 KB |

## `path-tree` inspired benches
### `path-tree` inspired benches

In a router of 320 templates, benchmark matching 80 paths.

| Library | Time | Alloc Count | Alloc Size | Dealloc Count | Dealloc Size |
|:-----------------|----------:|------------:|-----------:|--------------:|-------------:|
| wayfind | 4.6335 µs | 60 | 3.847 KB | 60 | 3.847 KB |
| path-tree | 7.0053 µs | 60 | 8.727 KB | 60 | 8.75 KB |
| matchit | 8.1093 µs | 141 | 19.09 KB | 141 | 19.11 KB |
| xitca-router | 9.9790 µs | 210 | 26.79 KB | 210 | 26.81 KB |
| ntex-router | 34.594 µs | 202 | 20.82 KB | 202 | 20.84 KB |
| route-recognizer | 67.158 µs | 2873 | 192.9 KB | 2873 | 206.1 KB |
| routefinder | 85.597 µs | 526 | 49.68 KB | 526 | 49.71 KB |
| actix-router | 186.41 µs | 2202 | 130.1 KB | 2202 | 130.1 KB |
| wayfind | 4.6982 µs | 60 | 3.847 KB | 60 | 3.847 KB |
| path-tree | 6.8691 µs | 60 | 8.727 KB | 60 | 8.75 KB |
| matchit | 8.2056 µs | 141 | 19.09 KB | 141 | 19.11 KB |
| xitca-router | 9.7926 µs | 210 | 26.79 KB | 210 | 26.81 KB |
| ntex-router | 33.477 µs | 202 | 20.82 KB | 202 | 20.84 KB |
| route-recognizer | 66.921 µs | 2873 | 192.9 KB | 2873 | 206.1 KB |
| routefinder | 84.616 µs | 526 | 49.68 KB | 526 | 49.71 KB |
| actix-router | 185.45 µs | 2201 | 130.1 KB | 2201 | 130.1 KB |
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Real-world projects often need fancy routing capabilities, such as projects port

The goal of `wayfind` is to remain competitive with the fastest libraries, while offering advanced routing features when needed. Unused features shouldn't impact performance - you only pay for what you use.

The downside of this approach is that we can't have as rich conflict detection as other routers.

## Features

### Dynamic Routing
Expand All @@ -49,12 +51,12 @@ fn main() -> Result<(), Box<dyn Error>> {
router.insert("/users/{id}", 1)?;
router.insert("/users/{id}/files/{filename}.{extension}", 2)?;

let search = router.search("/users/123")?.unwrap();
let search = router.search("/users/123").unwrap();
assert_eq!(*search.data, 1);
assert_eq!(search.template, "/users/{id}");
assert_eq!(search.parameters[0], ("id", "123"));

let search = router.search("/users/123/files/my.document.pdf")?.unwrap();
let search = router.search("/users/123/files/my.document.pdf").unwrap();
assert_eq!(*search.data, 2);
assert_eq!(search.template, "/users/{id}/files/{filename}.{extension}");
assert_eq!(search.parameters[0], ("id", "123"));
Expand Down Expand Up @@ -87,12 +89,12 @@ fn main() -> Result<(), Box<dyn Error>> {
router.insert("/files/{*slug}/delete", 1)?;
router.insert("/{*catch_all}", 2)?;

let search = router.search("/files/documents/reports/annual.pdf/delete")?.unwrap();
let search = router.search("/files/documents/reports/annual.pdf/delete").unwrap();
assert_eq!(*search.data, 1);
assert_eq!(search.template, "/files/{*slug}/delete");
assert_eq!(search.parameters[0], ("slug", "documents/reports/annual.pdf"));

let search = router.search("/any/other/path")?.unwrap();
let search = router.search("/any/other/path").unwrap();
assert_eq!(*search.data, 2);
assert_eq!(search.template, "/{*catch_all}");
assert_eq!(search.parameters[0], ("catch_all", "any/other/path"));
Expand Down Expand Up @@ -130,26 +132,26 @@ fn main() -> Result<(), Box<dyn Error>> {
router.insert("/users(/{id})", 1)?;
router.insert("/files/{*slug}/{file}(.{extension})", 2)?;

let search = router.search("/users")?.unwrap();
let search = router.search("/users").unwrap();
assert_eq!(*search.data, 1);
assert_eq!(search.template, "/users(/{id})");
assert_eq!(search.expanded, Some("/users"));

let search = router.search("/users/123")?.unwrap();
let search = router.search("/users/123").unwrap();
assert_eq!(*search.data, 1);
assert_eq!(search.template, "/users(/{id})");
assert_eq!(search.expanded, Some("/users/{id}"));
assert_eq!(search.parameters[0], ("id", "123"));

let search = router.search("/files/documents/folder/report.pdf")?.unwrap();
let search = router.search("/files/documents/folder/report.pdf").unwrap();
assert_eq!(*search.data, 2);
assert_eq!(search.template, "/files/{*slug}/{file}(.{extension})");
assert_eq!(search.expanded, Some("/files/{*slug}/{file}.{extension}"));
assert_eq!(search.parameters[0], ("slug", "documents/folder"));
assert_eq!(search.parameters[1], ("file", "report"));
assert_eq!(search.parameters[2], ("extension", "pdf"));

let search = router.search("/files/documents/folder/readme")?.unwrap();
let search = router.search("/files/documents/folder/readme").unwrap();
assert_eq!(*search.data, 2);
assert_eq!(search.template, "/files/{*slug}/{file}(.{extension})");
assert_eq!(search.expanded, Some("/files/{*slug}/{file}"));
Expand Down Expand Up @@ -232,18 +234,18 @@ fn main() -> Result<(), Box<dyn Error>> {
router.insert("/v2", 1)?;
router.insert("/v2/{*name:namespace}/blobs/{type}:{digest}", 2)?;

let search = router.search("/v2")?.unwrap();
let search = router.search("/v2").unwrap();
assert_eq!(*search.data, 1);
assert_eq!(search.template, "/v2");

let search = router.search("/v2/my-org/my-repo/blobs/sha256:1234567890")?.unwrap();
let search = router.search("/v2/my-org/my-repo/blobs/sha256:1234567890").unwrap();
assert_eq!(*search.data, 2);
assert_eq!(search.template, "/v2/{*name:namespace}/blobs/{type}:{digest}");
assert_eq!(search.parameters[0], ("name", "my-org/my-repo"));
assert_eq!(search.parameters[1], ("type", "sha256"));
assert_eq!(search.parameters[2], ("digest", "1234567890"));

let search = router.search("/v2/invalid repo/blobs/uploads")?;
let search = router.search("/v2/invalid repo/blobs/uploads");
assert!(search.is_none());

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion benches/matchit_criterion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn matchit_benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for path in black_box(paths()) {
let output = black_box(router.search(black_box(path)).unwrap().unwrap());
let output = black_box(router.search(black_box(path)).unwrap());
let _parameters: Vec<(&str, &str)> =
black_box(output.parameters.iter().map(|p| (p.0, p.1)).collect());
}
Expand Down
2 changes: 1 addition & 1 deletion benches/matchit_divan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn wayfind(bencher: divan::Bencher<'_, '_>) {

bencher.bench(|| {
for path in black_box(paths()) {
let output = black_box(router.search(black_box(path)).unwrap().unwrap());
let output = black_box(router.search(black_box(path)).unwrap());
let _parameters: Vec<(&str, &str)> =
black_box(output.parameters.iter().map(|p| (p.0, p.1)).collect());
}
Expand Down
2 changes: 1 addition & 1 deletion benches/matchit_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub fn paths() -> impl IntoIterator<Item = &'static str> {
"/user/repos",
"/repos/rust-lang/rust/stargazers",
"/orgs/rust-lang/public_members/nikomatsakis",
"/repos/rust-lang/rust/releases/1%2E51%2E0",
"/repos/rust-lang/rust/releases/1.51.0",
]
}

Expand Down
2 changes: 1 addition & 1 deletion benches/path_tree_criterion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn path_tree_benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for path in black_box(paths()) {
let output = black_box(router.search(black_box(path)).unwrap().unwrap());
let output = black_box(router.search(black_box(path)).unwrap());
let _parameters: Vec<(&str, &str)> =
black_box(output.parameters.iter().map(|p| (p.0, p.1)).collect());
}
Expand Down
2 changes: 1 addition & 1 deletion benches/path_tree_divan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn wayfind(bencher: divan::Bencher<'_, '_>) {

bencher.bench(|| {
for path in black_box(paths()) {
let output = black_box(router.search(black_box(path)).unwrap().unwrap());
let output = black_box(router.search(black_box(path)).unwrap());
let _parameters: Vec<(&str, &str)> =
black_box(output.parameters.iter().map(|p| (p.0, p.1)).collect());
}
Expand Down
2 changes: 1 addition & 1 deletion benches/path_tree_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn paths() -> impl IntoIterator<Item = &'static str> {
"/issues",
"/legacy/issues/search/rust-lang/rust/987/1597",
"/legacy/repos/search/1597",
"/legacy/user/email/rust%40rust-lang.org",
"/legacy/user/email/rust@rust-lang.org",
"/legacy/user/search/1597",
"/licenses",
"/licenses/mit",
Expand Down
2 changes: 1 addition & 1 deletion examples/oci/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl<'r> AppRouter<'r> {
return StatusCode::METHOD_NOT_ALLOWED.into_response();
};

let Ok(Some(search)) = router.search(&path) else {
let Some(search) = router.search(&path) else {
return StatusCode::NOT_FOUND.into_response();
};

Expand Down
12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 03b2360

Please sign in to comment.