perf: optimize normalize() to avoid allocation for already-clean paths#32
perf: optimize normalize() to avoid allocation for already-clean paths#32
Conversation
Merging this PR will improve performance by ×2.1
Performance Changes
Comparing Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR optimizes the normalize() method to avoid allocations for already-clean paths by changing the return type from PathBuf to Cow<'_, Path>. When a path doesn't need normalization (no consecutive slashes, no . or .. components, no trailing slashes), it returns Cow::Borrowed with zero allocation. Otherwise, it normalizes the path and returns Cow::Owned.
Changes:
- Changed
normalize()return type fromPathBuftoCow<'_, Path>for zero-allocation fast path - Added
needs_normalization()function for Unix and Windows to detect clean paths - Updated all call sites to use
.into_owned()wherePathBufis still needed - Added comprehensive tests for allocation behavior of clean vs dirty paths
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/sugar_path.rs | Updated trait definition to return Cow<'_, Path> instead of PathBuf |
| src/impl_sugar_path.rs | Added needs_normalization() check functions, updated normalize_inner() return type, and added .into_owned() calls at all usage sites |
| tests/normalize.rs | Added tests verifying clean paths return Cow::Borrowed and dirty paths return Cow::Owned |
| tests/deep_paths.rs | Refactored to store intermediate strings in variables due to lifetime requirements of the new API |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
## Summary Bump `sugar_path` from 1.2.1 to 2^ ### What changed in sugar_path 2.0 The 2.0 release is focused on reducing allocations in hot paths. The key optimizations: - **`normalize()` returns `Cow<'_, Path>` instead of `PathBuf`** — a `needs_normalization()` fast-path check (using `memchr`) detects already-clean paths and returns `Cow::Borrowed` with zero allocation ([#32](hyf0/sugar_path#32)) - **`absolutize()` / `absolutize_with()` return `Cow<'_, Path>`** — same idea: already-absolute clean paths are returned borrowed ([#34](hyf0/sugar_path#34)) - **`memchr`-accelerated fast path for `relative()`** — replaces the component-iterator approach with SIMD-accelerated `/` scanning, avoids the `absolutize()` → `current_dir()` syscall when both paths are already absolute, and uses `SmallVec<[&str; 8]>` to stay on the stack ([#27](hyf0/sugar_path#27)) - **Reduced allocations across the board** — reuse buffers, `SmallVec` for component lists, avoid `collect()` into `Vec` ([#26](hyf0/sugar_path#26)) ### Breaking change `normalize()`, `absolutize()`, and `absolutize_with()` now return `Cow<'_, Path>` instead of `PathBuf`. Call sites that need an owned `PathBuf` require `.into_owned()`, and chained operations like `.join().normalize().to_slash_lossy()` need to be split so the intermediate `Cow` lives long enough. ## Test plan - [x] CI passes (same API surface, just `Cow` unwrapping at call sites)
Closes #16
Summary
normalize()now returnsCow<'_, Path>instead ofPathBuf— already-clean paths are returned by reference with zero allocationneeds_normalization()fast-path check (both Unix and Windows)absolutize_with,relative) use.into_owned()wherePathBufis neededtests/deep_paths.rsTest plan
cargo test— all existing + new tests passcargo fmt --check— cleancargo clippy --all-targets— no warningscargo bench -- normalize— confirm speedup for already-clean paths🤖 Generated with Claude Code