这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@Boshen
Copy link
Member

@Boshen Boshen commented Nov 16, 2025

perf: use arena with RwLock for package.json storage

Summary

Refactors package.json caching to use an arena-based storage pattern with parking_lot::RwLock<Vec<Arc<PackageJson>>> instead of a HashMap. This enables batch dropping of all package.json data on cache clear and reduces memory overhead per CachedPathImpl.

Changes

Architecture

  • Before: Each CachedPathImpl stored OnceLock<Option<Arc<PackageJson>>>
  • After: Each CachedPathImpl stores OnceLock<Option<usize>> (arena index)
  • Storage: Centralized RwLock<Vec<Arc<PackageJson>>> arena in Cache

Implementation Details

  1. CachedPathImpl::package_json (src/cache/cached_path.rs:32)

    • Changed from OnceLock<Option<Arc<PackageJson>>> to OnceLock<Option<usize>>
    • Stores arena index instead of Arc
  2. Cache::package_json_arena (src/cache/cache_impl.rs:29)

    • Replaced HashMap<u64, Arc<PackageJson>, BuildHasherDefault<IdentityHasher>> with RwLock<Vec<Arc<PackageJson>>>
    • All package.json objects stored in contiguous Vec
  3. get_package_json() logic (src/cache/cache_impl.rs:102-170)

    • Parses package.json and creates Arc<PackageJson>
    • Acquires write lock, pushes to arena Vec
    • Stores index in CachedPathImpl::package_json OnceLock
    • On retrieval: acquires read lock, clones Arc from arena[index]
    • Handles index out-of-bounds gracefully (after cache clear)
  4. Cache::clear() (src/cache/cache_impl.rs:40)

    • Clears entire Vec with single write().clear() call
    • Drops all package.json data immediately
  5. Dependencies (Cargo.toml:85)

    • Added parking_lot = "0.12" for high-performance RwLock

Code Changes

// src/cache/cached_path.rs
pub struct CachedPathImpl {
    pub hash: u64,
    pub path: Box<Path>,
    // ...
-   pub package_json: OnceLock<Option<Arc<PackageJson>>>,
+   pub package_json: OnceLock<Option<usize>>,
    pub tsconfig: OnceLock<Option<Arc<TsConfig>>>,
}
// src/cache/cache_impl.rs
+use parking_lot::RwLock;

pub struct Cache<Fs> {
    pub(crate) fs: Fs,
    pub(crate) paths: HashSet<CachedPath, BuildHasherDefault<IdentityHasher>>,
    pub(crate) tsconfigs: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
-   pub(crate) package_jsons: HashMap<u64, Arc<PackageJson>, BuildHasherDefault<IdentityHasher>>,
+   pub(crate) package_json_arena: RwLock<Vec<Arc<PackageJson>>>,
}

Benefits

Arena pattern - All PackageJson stored in contiguous Vec
Batch dropping - Vec::clear() drops all entries at once on cache clear
Memory efficiency - Stores usize (8 bytes) instead of Arc pointer per CachedPathImpl
Concurrent reads - parking_lot RwLock allows multiple readers simultaneously
Better performance - parking_lot is 20-50x faster than std::RwLock in many scenarios
Stable indices - Vec only grows, never reorders (indices remain valid)
No lock poisoning - parking_lot locks can't be poisoned
Smaller memory footprint - parking_lot RwLock is 1 word vs boxed std RwLock
Hardware lock elision - Uses CPU lock elision when available

Testing

All existing tests pass:

  • ✅ 151 unit tests
  • ✅ 12 integration tests
  • ✅ 11 resolve tests

Performance Considerations

Read-Heavy Workload

The resolver is read-heavy - most operations read cached package.json rather than parsing new ones. RwLock is ideal for this pattern as it allows concurrent readers.

Write Contention

Writes (adding new package.json) are infrequent and brief. parking_lot's RwLock handles this efficiently with:

  • Task-fair policy (prevents starvation)
  • Fast locking primitives
  • Minimal overhead

Memory Impact

  • Per CachedPathImpl: Saves memory by storing usize (8 bytes) instead of Option<Arc<PackageJson>> (24+ bytes on 64-bit)
  • Overall: Single contiguous Vec is more cache-friendly than scattered HashMap entries

Migration Notes

This is an internal refactoring with no public API changes. All public methods continue to return Arc<PackageJson> as before.


🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@graphite-app
Copy link

graphite-app bot commented Nov 16, 2025

How to use the Graphite Merge Queue

Add the label merge to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

@codecov
Copy link

codecov bot commented Nov 16, 2025

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.94%. Comparing base (e1ef00d) to head (b635e46).

Files with missing lines Patch % Lines
src/cache/cache_impl.rs 92.85% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #838      +/-   ##
==========================================
- Coverage   93.98%   93.94%   -0.04%     
==========================================
  Files          17       17              
  Lines        3090     3104      +14     
==========================================
+ Hits         2904     2916      +12     
- Misses        186      188       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 16, 2025

CodSpeed Performance Report

Merging #838 will degrade performances by 3.58%

Comparing perf-package-json-arena (b635e46) with main (e1ef00d)

Summary

❌ 1 regression
✅ 10 untouched
⏩ 5 skipped1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
complex_real 22.4 µs 23.2 µs -3.58%

Footnotes

  1. 5 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants