perf: reduce redundant scans in path hot paths#47
Conversation
Merging this PR will improve performance by 20.78%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Simulation | owned_receiver/string_result |
401.7 ns | 460 ns | -12.68% |
| ⚡ | Simulation | non_utf8_lexically_clean |
2.5 µs | 1 µs | ×2.4 |
| ⚡ | Memory | non_utf8_absolute_target |
149 B | 64 B | ×2.3 |
| ⚡ | Simulation | borrowed_receiver/pathbuf_result |
2.5 µs | 1.7 µs | +45.76% |
| ⚡ | Simulation | normalize/current_directory_spellings[dot] |
534.4 ns | 439.2 ns | +21.7% |
| ⚡ | Simulation | clean_absolute |
1.3 µs | 1.1 µs | +18.06% |
| ⚡ | Simulation | clean_absolute |
1.4 µs | 1.2 µs | +16.65% |
| ⚡ | Simulation | normalize/canonical_leading_parents[leading_parents] |
1,088.6 ns | 935 ns | +16.43% |
| ⚡ | Simulation | non_utf8_absolute_target |
8.4 µs | 7.2 µs | +16.39% |
| ⚡ | Simulation | normalize/current_directory_spellings[dot_separator] |
507.2 ns | 440 ns | +15.28% |
| ⚡ | Simulation | normalize/leading_parent_prescan[clean_68b] |
1.3 µs | 1.1 µs | +13.68% |
| ⚡ | Simulation | string |
255.8 ns | 226.7 ns | +12.87% |
| ⚡ | Simulation | clean |
3.3 µs | 3 µs | +10.44% |
| ⚡ | Simulation | normalize/clean_rolldown[fixture_p50] |
1.2 µs | 1.1 µs | +10.08% |
| 👁 | Simulation | non_utf8_before_dirty_late |
2.7 µs | 3.2 µs | -16.27% |
| 👁 | Simulation | non_utf8_dirty_before |
2.5 µs | 3 µs | -16.27% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing agent/safe-path-hot-paths (1308526) with main (606e366)
Footnotes
-
38 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
998dfdd to
965f3d3
Compare
c76bdbb to
d2ed128
Compare
965f3d3 to
e16f1fb
Compare
d2ed128 to
500497a
Compare
dff41c7 to
f2dc484
Compare
f2dc484 to
2381636
Compare
2381636 to
f99b32c
Compare
There was a problem hiding this comment.
Pull request overview
This PR optimizes SugarPath’s path hot paths by reducing redundant scans and unnecessary allocations, especially around normalization classification and Windows relative-path separator handling, while explicitly keeping correctness for non-UTF-8/native-encoding inputs.
Changes:
- Switch normalization “needs work?” classification to inspect
OsStr::as_encoded_bytes()on conventional Unix/Windows (keeping Cygwin on the Unicode-validation path) and add exhaustive classifier-vs-full-normalizer tests. - Preserve
Cow<Path>through relative-path resolution so clean intermediates remain borrowed unless an owned result is required. - Reuse owned Windows slash-form relative buffers by replacing
/→\in-place (no extra allocation) and update invalid-encoding tests + allocation snapshots accordingly.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/impl_sugar_path.rs |
Implements encoded-byte normalization classifier changes, retains Cow<Path> in relative fallbacks, and adds in-place Windows separator replacement + tests. |
tests/invalid_encoding.rs |
Adds coverage ensuring clean invalid-encoding inputs can borrow while dirty spellings rebuild, on Unix (non-Cygwin) and Windows. |
benchmarks/allocations/x86_64-unknown-linux-gnu.snap |
Updates allocation baselines reflecting reduced allocations for invalid-encoding normalization/relative cases. |
benchmarks/allocations/x86_64-pc-windows-msvc.snap |
Updates allocation baselines reflecting reduced allocations for invalid-encoding normalization/relative and Windows separator-handling cases. |
.agents/docs/performance-strategy.md |
Documents the new classifier strategy, tradeoffs, and supporting benchmark evidence/constraints. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Status
Draft — waiting for baseline #48 to land before retargeting. The three known regressions are all explicitly non-UTF-8 and are not performance acceptance gates under the vouched project rule. This PR is independent of #46.
Summary
OsStr::as_encoded_bytes()on conventional Unix and Windows; keep Cygwin on the existing Unicode-validation gateCow<Path>through relative fallback resolution so clean normalized intermediates are cloned only when the final result must own themnormalize_inner, and single-pass borrowed slash conversion; the rejected Unix scanner and double-scan conversion are absentThe diff adds no
unsafecode.Performance decision
SugarPath's vouched performance rule treats paths that can be represented losslessly as valid UTF-8 as the default performance target. An optimization remains valid when native non-UTF-8 inputs become slower, provided their exact path data, results, errors, and safety remain correct.
Under that rule, keep the encoded-byte classifier,
Cow<Path>propagation, and Windows owned-buffer reuse. Do not restore the Unix fixed-width scanner. Keep every non-UTF-8 benchmark independently visible, but do not add complexity or sacrifice demonstrated UTF-8 gains solely to equalize those rows without consumer evidence.The recorded Rolldown trace contained 4,901 normalization calls, all clean valid inputs, and no dirty or non-UTF-8 normalization inputs.
Performance evidence
The final stacked CodSpeed comparison reports 14 improvements, 3 regressions, 211 untouched benchmarks, and 36 skipped benchmarks. Every regressed row now identifies
non_utf8directly in its leaf name.Improvements include:
non_utf8_lexically_clean: 2.5 µs to 1.0 µs (×2.4)non_utf8_absolute_target: 8.4 µs to 7.2 µs (+17.33%)×2.3)normalize/owned_input/module_loader_hot/borrowed_receiver/pathbuf_result:+48.25%normalize/rolldown_corpus/clean:+10.44%The three regressions are:
dirty_before_non_utf8: 2.5 µs to 3.0 µs (-16.27%)non_utf8_before_dirty_late: 2.7 µs to 3.2 µs (-16.27%)borrowed_receiver/non_utf8_strict: 441.1 ns to 499.4 ns (-11.68%)The Unix implementation exercised by
borrowed_receiver/non_utf8_strictremains the samePath::to_str().map(Cow::Borrowed)path between BASE and HEAD, so that result reflects whole-crate compiled layout rather than added slash-conversion work.CodSpeed warns that BASE and HEAD used different runtime environments. The three regression values and the principal classifier results reproduced across consecutive runs, so the rows remain useful directional evidence while exact gains should not be treated as universal constants.
The original unacceptable regressions are gone: no valid-UTF-8 relative row regresses by 10%–26%, the parent-cancellation regression from forcing
normalize_innerout of line is gone, and the valid-UTF-8to_slashregression from scanning twice is gone.Native Linux and Windows allocation checks both pass with no allocation-count regression. Clean non-UTF-8 normalization changes from one allocation to zero, owned non-UTF-8 consuming normalization changes from one to zero, and non-UTF-8 absolute relative changes from three allocations to one. Windows also reduces dotted relative from four to three allocations, absolute forward-slash relative from two to one, and root-relative from three to one.
Release
cargo llvm-lineschanges from 16,682 to 16,665 lines and from 431 to 430 copies, so the retained implementation does not add the code-size increase from the removed scanner.Correctness and safety
unsafe; all new implementation code uses safeCow, encoded-byte inspection, and owned-buffer mutationValidation
cargo fmt --all --checkcargo test --locked --workspace --all-featurescargo clippy --locked --workspace --all-targets --all-features -- -D warningsRUSTDOCFLAGS="-D warnings" cargo doc --locked --workspace --all-features --no-depscargo bench --locked --no-runx86_64-unknown-uefi,x86_64-pc-windows-gnu, andx86_64-pc-windows-msvcThe CodSpeed summary remains red and has not been acknowledged, so all three non-UTF-8 regressions stay visible while this PR is Draft.
Draft gates
main