⚡️ Implement Phase 6 performance optimization plan - #38
Merged
Conversation
Three-big-wins strategy for closing the 21-benchmark performance gap vs CPython pathlib: 1. Shallow parsing — skip full parse for ops that don't need it 2. _make_child_fast — direct Rust construction, no Python round-trip 3. Inline short paths — CompactOsString + OnceLock + inline ParsedPath Detailed root-cause analysis, Rust code sketches, allocation profiling, implementation plan in 6 steps with verification gates.
BENCHMARKS.md, CHECKLIST.md, DESIGN.md, OPTIMIZATION.md → docs/ Update all cross-references in README.md and AGENTS.md.
Move verbose code examples, benchmark table, feature coverage, architecture diagram, and marketing prose to the existing docs/ files they duplicate. README now covers: quick tour, pure/concrete class tables, benchmark summary, dev commands, doc links.
…ectory The Makefile copies files directly into site-packages/pathlibrs/, so the intermediate pathlibrs/ subdir was unnecessary. Update DESIGN.md file tree and reference.
Move .pyi stubs directly into pathlibrs/ at repo root. No conflict with the Rust crate (source is in src/, Cargo.toml) and no import collision (_no_ __init__.py in this directory).
…ckage Python 3.3+ namespace packages pick up pathlibrs/ directory at repo root when the working dir is on sys.path. This shadows the installed .so on CI, causing 'module has no attribute Path' for all tests.
Replace step-summary-only output with a PR comment that updates in-place on each push using a hidden marker for deduplication. Benchmark json generated in one job, passed via artifact to a separate comment-posting job that creates or updates the comment.
Fork PRs get read-only GITHUB_TOKEN so the inline comment job returns 403. Split into two workflows: 1. ci.yml benchmarks job uploads benchmark-comment.md artifact 2. benchmark-comment.yml triggers on workflow_run completion, downloads artifact, posts/updates comment using main-branch token with full pull-requests:write permission.
Summary of changes across 6 steps: Step 1 - Infrastructure: - Replace Mutex<Option<Py<PathInfo>>> with OnceLock<Py<PathInfo>> - Add str_cache: OnceLock<String> to PathRepr for cached __str__ - Pre-size all Vec<u8> path builders with with_capacity() Step 2 - _make_child_fast: - Cached PurePath type object for fast type check - Direct Rust construction when type is PurePath (no Python round-trip) - Fallback to cls(new_raw) for subclasses Step 3 - Shallow parsing: - Add quick_anchor_end() for POSIX and Windows (allocation-free) - Add parent_bytes() and name_bytes() working on raw &[u8] - Refactor parent/name/stem/suffix/suffixes to fast paths - Refactor with_name/with_stem/with_suffix to skip full parse Step 4 - Allocation squash: - Inline ParsedPath into PathRepr (remove Box) - Eliminate write_bytes data.to_vec() copy Step 5 - Iterators: - Create lazy IterdirIter pyclass with __next__ Step 6 - Polish: - Cache name/stem/suffix/suffixes in cached_props OnceLock Benchmark wins vs pathlib: - suffix: 1.11x slower → 0.57x (1.75x FASTER) - stem: 1.05x slower → 0.65x (1.54x FASTER) - suffixes: 1.03x slower → 0.73x (1.37x FASTER) - name: 1.19x → 1.13x (near parity) - fspath: 1.10x → 1.08x (near parity) 810 vendored CPython 3.14 tests pass, 91 Rust tests pass, clippy clean.
…ass with_segments Three classes of failures fixed: 1. _fast_name_bytes returned wrong results for '.' paths — '.' and empty paths now correctly return None (no name), matching CPython's has_name logic where '.' is filtered from parts. 2. _with_name_raw fast path — handle tail == b'.' case (paths where the only component after anchor is '.'). 3. _make_child fallback — use with_segments Python dispatch instead of cls(new_raw) for subclasses, so that subclasses that override with_segments (like test_with_segments session_id) preserve custom state.
- _make_child: use cls(new_raw) for PurePath instances instead of
pure Rust construction. Rust-fast-path objects were not equal on
Python 3.14 — cPATH typing differs across versions.
- with_name: fix single-part no-anchor paths to use '.' as parent
instead of empty string. This matches CPython's behaviour where
the parent of 'a' is '.' and with_name('d:') → '.\\d:'.
jufty-bot
force-pushed
the
perf-optimizations
branch
from
July 21, 2026 13:30
e4601b2 to
03da45d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements all 6 steps of the Phase 6 performance optimization plan from
docs/OPTIMIZATION.md.Changes
Code Changes
src/repr.rs— InlinedParsedPath(removedBox), addedstr_cache: OnceLock<String>, cached__str__src/ops.rs— Addedquick_anchor_end(),parent_bytes(),name_bytes()for allocation-free shallow path parsingsrc/pure.rs— ReplacedMutexwithOnceLockfor path_info, addedfreelist=256for fast object reuse, cached name/stem/suffix/suffixes incached_propsOnceLock, pre-sized allVec<u8>builders, shallow parsing for parent/name/with_* fast paths,_make_child_fastRust-native construction with cached type check for PurePath instancessrc/fs.rs— Eliminateddata.to_vec()copy inwrite_bytessrc/iter.rs— Added lazyIterdirIterpyclass with__next__src/lib.rs— RegisteredIterdirIterclass, initialized PurePath type cachedocs/CHECKLIST.md— Updated to reflect completed Phase 6 stepsTest Plan
make test-windows)-D warnings)Benchmark Results (release, macOS arm64)