Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor priority logic. #229

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions BENCHMARKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,49 @@ 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.
| 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
### `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 | 296.31 ns | 4 | 288 B | 4 | 288 B |
| matchit | 363.11 ns | 4 | 448 B | 4 | 448 B |
| path-tree | 397.93 ns | 4 | 448 B | 4 | 448 B |
| xitca-router | 481.52 ns | 7 | 832 B | 7 | 832 B |
| ntex-router | 1.9511 µs | 18 | 1.28 KB | 18 | 1.28 KB |
| route-recognizer | 2.7551 µs | 160 | 8.537 KB | 160 | 8.537 KB |
| routefinder | 5.7907 µs | 67 | 5.056 KB | 67 | 5.056 KB |
| actix-router | 19.619 µ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 | 3.9946 µs | 59 | 3.808 KB | 59 | 3.808 KB |
| path-tree | 6.3520 µs | 59 | 8.704 KB | 59 | 8.704 KB |
| matchit | 7.7859 µs | 140 | 19.07 KB | 140 | 19.07 KB |
| xitca-router | 9.4098 µs | 209 | 26.77 KB | 209 | 26.77 KB |
| ntex-router | 31.865 µs | 201 | 20.8 KB | 201 | 20.8 KB |
| route-recognizer | 62.792 µs | 2872 | 192.9 KB | 2872 | 206.1 KB |
| routefinder | 80.345 µs | 525 | 49.66 KB | 525 | 49.66 KB |
| actix-router | 174.04 µs | 2201 | 130.1 KB | 2201 | 130.1 KB |

### `wayfind` benches

TODO
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.

38 changes: 20 additions & 18 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 Expand Up @@ -334,13 +336,6 @@ fn main() -> Result<(), Box<dyn Error>> {

insta::assert_snapshot!(router, @r"
/
├─ user [*]
│ ╰─ /
│ ├─ createWithList [*]
│ ├─ log
│ │ ├─ out [*]
│ │ ╰─ in [*]
│ ╰─ {username} [*]
├─ pet [*]
│ ╰─ /
│ ├─ findBy
Expand All @@ -353,6 +348,13 @@ fn main() -> Result<(), Box<dyn Error>> {
│ ╰─ order [*]
│ ╰─ /
│ ╰─ {orderId} [*]
├─ user [*]
│ ╰─ /
│ ├─ createWithList [*]
│ ├─ log
│ │ ├─ in [*]
│ │ ╰─ out [*]
│ ╰─ {username} [*]
╰─ {*catch_all} [*]
");

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