Skip to content

perf(alias): scan a packed first-byte array instead of striding over alias entries#1260

Merged
Boshen merged 1 commit into
mainfrom
perf-alias-first-byte-scan
Jul 2, 2026
Merged

perf(alias): scan a packed first-byte array instead of striding over alias entries#1260
Boshen merged 1 commit into
mainfrom
perf-alias-first-byte-scan

Conversation

@Boshen

@Boshen Boshen commented Jul 2, 2026

Copy link
Copy Markdown
Member

What

Turn CompiledAlias from a Vec<CompiledAliasEntry> type alias into a struct that packs each entry's cached first byte into a contiguous side array plus a 256-bit presence mask:

  • may_match — O(1) "no entry can match" exit via the mask, without touching any entry.
  • matching — scans the packed byte array and only dereferences entries whose first byte is compatible.
  • any_key_matches — the per-file-candidate check in load_browser_field_or_alias jumps straight to candidate indices with memchr when no always-evaluated entry exists.

Matching semantics are unchanged: candidates are still confirmed with key_matches in declaration order (first match wins), and empty-key/empty-wildcard-prefix entries remain always-evaluated (stored as 0 in the byte array; a key genuinely starting with NUL also maps to 0 and just loses its fast-reject).

Why

#1142 cached the first byte on each entry, but the reject loop still strides over every ~100-byte CompiledAliasEntry — once per file candidate in load_browser_field_or_alias and once per require in load_alias. With the benchmark's 23-entry alias list that is ~2.5 KiB of scattered loads per scan.

samply profiles of the bench workloads attribute 12.5% of CPU on resolver_memory/single-thread and ~31% on resolver_memory/resolve from symlinks to this scan (key_matches self time plus the any() loop at lib.rs:935). After the change, key_matches drops out of the top self-time entries entirely in both profiles.

Measured impact (criterion, macOS arm64)

bench before after delta
resolver_memory/single-thread 26.3 µs 21.9 µs −17%
resolver_memory/multi-thread 23.4 µs 20.6 µs −12%
resolver_memory/resolve from symlinks 5.0 ms 3.5 ms −30%
resolver_real/single-thread 27.0 µs 23.5 µs −13%
resolver_real/multi-thread 23.4 µs 21.0 µs −10%
resolver_real/resolve from symlinks 10.5 ms 7.5 ms −29%

tsconfig_paths_aliases_memory, package_json_deserialization, and resolver_memory/find tsconfig are unchanged. With an empty alias list the new fields are an empty box + 32 zero bytes and may_match exits on the first mask word.

…alias entries

The alias fast-reject loop touched every ~100-byte CompiledAliasEntry per
file candidate (load_browser_field_or_alias) and per require (load_alias),
even though the cached first byte rejects nearly all entries. Profiling the
resolver benchmark workloads attributed 12.5% (single-thread) and 31%
(resolve from symlinks) of CPU to this pointer-chasing scan.

Turn CompiledAlias into a struct that packs each entry's first byte into a
contiguous side array plus a 256-bit presence mask:

- may_match: O(1) exit when no entry's first byte is compatible.
- matching: scans the byte array and only dereferences candidate entries.
- any_key_matches: jumps straight to candidate indices with memchr on the
  per-candidate path.

Criterion (macOS arm64):

- resolver_memory/single-thread:        26.3 -> 21.9 us  (-17%)
- resolver_memory/resolve from symlinks: 5.0 -> 3.5 ms   (-30%)
- resolver_memory/multi-thread:         23.4 -> 20.6 us  (-12%)
- resolver_real/single-thread:          27.0 -> 23.5 us  (-13%)
- resolver_real/resolve from symlinks:  10.5 -> 7.5 ms   (-29%)
- resolver_real/multi-thread:           23.4 -> 21.0 us  (-10%)
- tsconfig/package_json groups: unchanged
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.85%. Comparing base (af3f527) to head (ace9ef9).

Files with missing lines Patch % Lines
src/alias.rs 94.87% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1260      +/-   ##
==========================================
- Coverage   93.87%   93.85%   -0.02%     
==========================================
  Files          21       21              
  Lines        4277     4313      +36     
==========================================
+ Hits         4015     4048      +33     
- Misses        262      265       +3     

☔ View full report in Codecov by Harness.
📢 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

codspeed-hq Bot commented Jul 2, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 12.67%

⚡ 9 improved benchmarks
✅ 12 untouched benchmarks
⏩ 5 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
resolver_real[resolve from symlinks] 33.1 ms 25.5 ms +30.14%
resolver_memory[resolve from symlinks] 33.2 ms 25.6 ms +29.66%
resolver_memory[multi-thread] 284.7 µs 244.5 µs +16.44%
resolver_memory[single-thread] 258.1 µs 233.8 µs +10.38%
resolver_real[single-thread] 259.8 µs 235.9 µs +10.13%
resolver_real[multi-thread] 271.2 µs 248.4 µs +9.16%
pm/pnpm-isolated 1.1 ms 1 ms +4.99%
pm/npm-flat 928.7 µs 898.4 µs +3.38%
pm/pnpm-hoisted 1 ms 1 ms +3.36%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing perf-alias-first-byte-scan (ace9ef9) with main (af3f527)

Open in CodSpeed

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.

@Boshen
Boshen merged commit 4837a2e into main Jul 2, 2026
18 checks passed
@Boshen
Boshen deleted the perf-alias-first-byte-scan branch July 2, 2026 01:27
@oxc-guard oxc-guard Bot mentioned this pull request Jul 2, 2026
Boshen pushed a commit that referenced this pull request Jul 2, 2026
## 🤖 New release

* `oxc_resolver`: 11.22.0 -> 11.23.0
* `oxc_resolver_napi`: 11.22.0 -> 11.23.0

<details><summary><i><b>Changelog</b></i></summary><p>

## `oxc_resolver`

<blockquote>

##
[11.23.0](v11.22.0...v11.23.0)
- 2026-07-02

### <!-- 0 -->🚀 Features

- *(tsconfig)* expose outDir, declarationDir, resolveJsonModule, checkJs
([#1257](#1257)) (by
@Boshen)

### <!-- 1 -->🐛 Bug Fixes

- *(exports)* propagate the last error in array target resolution
([#1267](#1267)) (by
@Boshen)

### <!-- 2 -->🚜 Refactor

- deduplicate cross-file helpers, drop stale lint allows
([#1269](#1269)) (by
@Boshen)
- *(dts)* deduplicate package entry selection and remove dead code
([#1266](#1266)) (by
@Boshen)
- *(tsconfig)* skip TsConfig deep clone for non-root configs
([#1265](#1265)) (by
@Boshen)
- *(alias)* compile fallback aliases once at construction
([#1264](#1264)) (by
@Boshen)

### <!-- 4 -->⚡ Performance

- *(alias)* scan a packed first-byte array instead of striding over
alias entries
([#1260](#1260)) (by
@Boshen)

### Contributors

* @Boshen
</blockquote>



</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

Co-authored-by: oxc-guard[bot] <276638029+oxc-guard[bot]@users.noreply.github.com>
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.

1 participant