Skip to content

perf: optimize lru-cache.ts#7455

Open
schiller-manuel wants to merge 1 commit into
mainfrom
perf-lru-cache
Open

perf: optimize lru-cache.ts#7455
schiller-manuel wants to merge 1 commit into
mainfrom
perf-lru-cache

Conversation

@schiller-manuel
Copy link
Copy Markdown
Collaborator

@schiller-manuel schiller-manuel commented May 20, 2026

LRU Cache Optimization Results

BEFORE

State: test/benchmark support changes applied, implementation stashed to baseline.

Tests

  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache -- tests/lru.test.ts: failed, 1 failed / 13 total.
  • Failure: updates existing entries without evicting when full, expected cache.get('a') to be 1, received undefined.
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache: failed, 1 failed / 1104 total.
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:types --outputStyle=stream --skipRemoteCache: passed.
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:eslint --outputStyle=stream --skipRemoteCache: passed with 20 existing warnings.

Performance

Command: CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:perf --outputStyle=stream --skipRemoteCache -- tests/lru-cache.bench.ts

Case hz mean rme samples
newest hit 18,518.89 0.0540 +/-0.58% 9,260

Bundle Size

Command: CI=1 NX_DAEMON=false pnpm nx run @benchmarks/bundle-size:build --outputStyle=stream --skipRemoteCache --skipNxCache -- --scenario react-router.minimal >/tmp/lru-before-targeted-bundle.log 2>&1 && pnpm benchmark:bundle-size:query --id react-router.minimal

  • react-router.minimal gzip=89421 initial=89281 raw=280786 brotli=77765

AFTER

State: test/benchmark support changes plus optimized LRU implementation applied.

Tests

  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache -- tests/lru.test.ts: passed, 13 passed / 13 total.
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache: passed, 1101 passed / 3 expected fail / 1104 total.
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:types --outputStyle=stream --skipRemoteCache: passed.
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:eslint --outputStyle=stream --skipRemoteCache: passed with 20 existing warnings.

Performance

Command: CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:perf --outputStyle=stream --skipRemoteCache -- tests/lru-cache.bench.ts

Case hz mean rme samples
newest hit 19,796.85 0.0505 +/-0.43% 9,899

Performance Comparison

| Case | BEFORE hz | AFTER hz | hz delta | BEFORE mean | AFTER mean | mean delta | | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| newest hit | 18,518.89 | 19,796.85 | +6.90% | 0.0540 | 0.0505 | -6.48% | | rotating hit | 13,741.92 | 14,971.94 | +8.95% | 0.0728 | 0.0668 | -8.24% | | update newest while full | 17,309.32 | 19,253.35 | +11.23% | 0.0578 | 0.0519 | -10.21% | | update oldest while full | 17,448.76 | 18,284.35 | +4.79% | 0.0573 | 0.0547 | -4.54% | | update rotating entries while full | 9,113.98 | 14,969.14 | +64.24% | 0.1097 | 0.0668 | -39.11% | | miss-heavy get | 78,647.37 | 78,455.30 | -0.24% | 0.0127 | 0.0127 | 0.00% | | insert churn | 14,705.09 | 14,950.34 | +1.67% | 0.0680 | 0.0669 | -1.62% | | mixed workload | 41,227.14 | 38,459.24 | -6.71% | 0.0243 | 0.0260 | +7.00% |

Notes: update rotating entries while full improves materially because the old implementation evicted before checking existing entries. mixed workload regressed in this run and has moderate noise (AFTER rme +/-2.41%), so treat as directional.

Bundle Size

Command: CI=1 NX_DAEMON=false pnpm nx run @benchmarks/bundle-size:build --outputStyle=stream --skipRemoteCache --skipNxCache -- --scenario react-router.minimal >/tmp/lru-after-targeted-bundle.log 2>&1 && pnpm benchmark:bundle-size:query --id react-router.minimal && pnpm benchmark:bundle-size:diff --baseline /var/folders/6f/2t42ntqs4yv4h6qwzbh5pmcm0000gn/T/opencode/lru-base-current.json --id react-router.minimal

  • react-router.minimal gzip=89407 initial=89268 raw=280685 brotli=77653
  • Diff vs BEFORE: gzip -14, initial -13, raw -101, brotli -112.

Summary by CodeRabbit

  • Refactor

    • LRU cache implementation updated for improved reliability.
  • Tests

    • Added comprehensive benchmark suite and expanded test coverage for cache functionality.

Review Change Stack

# LRU Cache Optimization Results

## BEFORE

State: test/benchmark support changes applied, implementation stashed to baseline.

### Tests

- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache -- tests/lru.test.ts`: failed, 1 failed / 13 total.
- Failure: `updates existing entries without evicting when full`, expected `cache.get('a')` to be `1`, received `undefined`.
- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache`: failed, 1 failed / 1104 total.
- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:types --outputStyle=stream --skipRemoteCache`: passed.
- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:eslint --outputStyle=stream --skipRemoteCache`: passed with 20 existing warnings.

### Performance

Command: `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:perf --outputStyle=stream --skipRemoteCache -- tests/lru-cache.bench.ts`

| Case | hz | mean | rme | samples |
| --- | ---: | ---: | ---: | ---: |
| newest hit | 18,518.89 | 0.0540 | +/-0.58% | 9,260 |
| rotating hit | 13,741.92 | 0.0728 | +/-3.08% | 6,871 |
| update newest while full | 17,309.32 | 0.0578 | +/-0.67% | 8,655 |
| update oldest while full | 17,448.76 | 0.0573 | +/-1.65% | 8,725 |
| update rotating entries while full | 9,113.98 | 0.1097 | +/-0.50% | 4,557 |
| miss-heavy get | 78,647.37 | 0.0127 | +/-0.73% | 39,324 |
| insert churn | 14,705.09 | 0.0680 | +/-3.13% | 7,353 |
| mixed workload | 41,227.14 | 0.0243 | +/-0.25% | 20,614 |

### Bundle Size

Command: `CI=1 NX_DAEMON=false pnpm nx run @benchmarks/bundle-size:build --outputStyle=stream --skipRemoteCache --skipNxCache -- --scenario react-router.minimal >/tmp/lru-before-targeted-bundle.log 2>&1 && pnpm benchmark:bundle-size:query --id react-router.minimal`

- `react-router.minimal gzip=89421 initial=89281 raw=280786 brotli=77765`

## AFTER

State: test/benchmark support changes plus optimized LRU implementation applied.

### Tests

- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache -- tests/lru.test.ts`: passed, 13 passed / 13 total.
- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache`: passed, 1101 passed / 3 expected fail / 1104 total.
- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:types --outputStyle=stream --skipRemoteCache`: passed.
- `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:eslint --outputStyle=stream --skipRemoteCache`: passed with 20 existing warnings.

### Performance

Command: `CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:perf --outputStyle=stream --skipRemoteCache -- tests/lru-cache.bench.ts`

| Case | hz | mean | rme | samples |
| --- | ---: | ---: | ---: | ---: |
| newest hit | 19,796.85 | 0.0505 | +/-0.43% | 9,899 |
| rotating hit | 14,971.94 | 0.0668 | +/-0.44% | 7,486 |
| update newest while full | 19,253.35 | 0.0519 | +/-1.03% | 9,627 |
| update oldest while full | 18,284.35 | 0.0547 | +/-3.18% | 9,143 |
| update rotating entries while full | 14,969.14 | 0.0668 | +/-0.92% | 7,485 |
| miss-heavy get | 78,455.30 | 0.0127 | +/-0.26% | 39,228 |
| insert churn | 14,950.34 | 0.0669 | +/-4.60% | 7,476 |
| mixed workload | 38,459.24 | 0.0260 | +/-2.41% | 19,230 |

### Performance Comparison

| Case | BEFORE hz | AFTER hz | hz delta | BEFORE mean | AFTER mean | mean delta |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| newest hit | 18,518.89 | 19,796.85 | +6.90% | 0.0540 | 0.0505 | -6.48% |
| rotating hit | 13,741.92 | 14,971.94 | +8.95% | 0.0728 | 0.0668 | -8.24% |
| update newest while full | 17,309.32 | 19,253.35 | +11.23% | 0.0578 | 0.0519 | -10.21% |
| update oldest while full | 17,448.76 | 18,284.35 | +4.79% | 0.0573 | 0.0547 | -4.54% |
| update rotating entries while full | 9,113.98 | 14,969.14 | +64.24% | 0.1097 | 0.0668 | -39.11% |
| miss-heavy get | 78,647.37 | 78,455.30 | -0.24% | 0.0127 | 0.0127 | 0.00% |
| insert churn | 14,705.09 | 14,950.34 | +1.67% | 0.0680 | 0.0669 | -1.62% |
| mixed workload | 41,227.14 | 38,459.24 | -6.71% | 0.0243 | 0.0260 | +7.00% |

Notes: `update rotating entries while full` improves materially because the old implementation evicted before checking existing entries. `mixed workload` regressed in this run and has moderate noise (`AFTER rme +/-2.41%`), so treat as directional.

### Bundle Size

Command: `CI=1 NX_DAEMON=false pnpm nx run @benchmarks/bundle-size:build --outputStyle=stream --skipRemoteCache --skipNxCache -- --scenario react-router.minimal >/tmp/lru-after-targeted-bundle.log 2>&1 && pnpm benchmark:bundle-size:query --id react-router.minimal && pnpm benchmark:bundle-size:diff --baseline /var/folders/6f/2t42ntqs4yv4h6qwzbh5pmcm0000gn/T/opencode/lru-base-current.json --id react-router.minimal`

- `react-router.minimal gzip=89407 initial=89268 raw=280685 brotli=77653`
- Diff vs BEFORE: `gzip -14`, `initial -13`, `raw -101`, `brotli -112`.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 598a870c-f570-4d41-928d-e830ed964c54

📥 Commits

Reviewing files that changed from the base of the PR and between 7df0d02 and 1cd3483.

📒 Files selected for processing (3)
  • packages/router-core/src/lru-cache.ts
  • packages/router-core/tests/lru-cache.bench.ts
  • packages/router-core/tests/lru.test.ts

📝 Walkthrough

Walkthrough

The PR refactors the LRU cache implementation to decouple update semantics from eviction: updating existing keys now calls touch to reorder without evicting, while eviction occurs only during new key insertion. The touch helper was rewritten to properly detach and reattach entries with correct pointer management, and get now uses explicit early returns. Comprehensive tests and benchmarks validate the new behavior across ordering, updates, edge cases, and performance scenarios.

Changes

LRU Cache Refactoring

Layer / File(s) Summary
Core implementation refactoring
packages/router-core/src/lru-cache.ts
touch helper detaches entries from current position and reattaches at newest end with explicit pointer updates and early returns. get uses explicit early-return branches. set decouples updates from eviction: existing keys update and reorder without evicting; eviction occurs only on new key insertion with adjusted pointer updates for oldest node deletion.
Test coverage for LRU cache behavior
packages/router-core/tests/lru.test.ts
New tests validate that missing-key get does not reorder, updates move existing keys to most-recently-used without evicting when full, updates do not duplicate entries, get promotion on middle entries triggers correct eviction on subsequent inserts, clear() allows cache reuse, size-1 edge case is handled, undefined values cache distinctly from missing keys, and non-string keys use identity semantics.
Performance benchmarks for LRU cache
packages/router-core/tests/lru-cache.bench.ts
Vitest benchmark suite with prefill helper and scenarios covering newest hits, rotating hits, updates while full, miss-heavy reads, insert churn, and mixed workloads at capacities of 1000 and 64 entries.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 The cache now dances with finesse so fine,
Touch detaches, reattaches in perfect line,
Updates stay in place while new entries arrive,
Eviction waits its turn—how the linked list thrives!
Tests and benchmarks prove the rhythm's right,
This refactored LRU shines so bright! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'perf: optimize lru-cache.ts' accurately reflects the main change: an optimized LRU cache implementation with demonstrated performance improvements and additional benchmarks/tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf-lru-cache

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 20, 2026

View your CI Pipeline Execution ↗ for commit 1cd3483

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 8m 42s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2m 20s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-20 22:26:47 UTC

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

1 package(s) bumped directly, 8 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/router-generator 1.167.7 → 1.167.8 Changeset
@tanstack/react-start 1.168.8 → 1.168.9 Dependent
@tanstack/react-start-rsc 0.1.8 → 0.1.9 Dependent
@tanstack/router-cli 1.167.7 → 1.167.8 Dependent
@tanstack/router-plugin 1.168.8 → 1.168.9 Dependent
@tanstack/router-vite-plugin 1.167.8 → 1.167.9 Dependent
@tanstack/solid-start 1.168.8 → 1.168.9 Dependent
@tanstack/start-plugin-core 1.171.1 → 1.171.2 Dependent
@tanstack/vue-start 1.168.8 → 1.168.9 Dependent

@schiller-manuel schiller-manuel requested a review from Sheraff May 20, 2026 22:17
@github-actions
Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: d8c83c1aa939
  • Measured at: 2026-05-20T22:19:01.705Z
  • Baseline source: history:7df0d02bfb14
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Initial gzip Raw Brotli Trend
react-router.minimal 87.28 KiB -22 B (-0.02%) 87.14 KiB 273.92 KiB 75.90 KiB ████████▄▄▄▁
react-router.full 90.80 KiB -18 B (-0.02%) 90.67 KiB 285.41 KiB 78.91 KiB ▇▇▇▇▇▇▇█▃▃▃▁
solid-router.minimal 35.51 KiB -18 B (-0.05%) 35.39 KiB 106.23 KiB 31.94 KiB ▆▆▆▆████▃▃▃▁
solid-router.full 40.25 KiB -23 B (-0.06%) 40.12 KiB 120.43 KiB 36.19 KiB ▆▆▆▆▇▇▇▇███▁
vue-router.minimal 53.29 KiB -32 B (-0.06%) 53.16 KiB 151.36 KiB 47.91 KiB ███████▆▇▇▇▁
vue-router.full 58.42 KiB -30 B (-0.05%) 58.29 KiB 167.52 KiB 52.36 KiB ███████▇███▁
react-start.minimal 102.00 KiB -24 B (-0.02%) 101.87 KiB 322.37 KiB 88.25 KiB ▄▄▄▄▆▆▆▆███▁
react-start.deferred-hydration 103.04 KiB -27 B (-0.03%) 102.17 KiB 324.01 KiB 89.17 KiB ████▇▇▇▁
react-start.full 105.40 KiB -18 B (-0.02%) 105.26 KiB 332.68 KiB 91.03 KiB ▇▇▇▇████▄▄▄▁
react-start.rsbuild.minimal 99.63 KiB -9 B (-0.01%) 99.46 KiB 316.83 KiB 85.75 KiB ▆▆▆▆▆▆▆▂███▁
react-start.rsbuild.full 102.92 KiB -7 B (-0.01%) 102.75 KiB 327.24 KiB 88.54 KiB ███████▅▆▆▆▁
solid-start.minimal 49.66 KiB -17 B (-0.03%) 49.53 KiB 152.35 KiB 43.83 KiB ▂▂▂▂███▇▅▅▅▁
solid-start.deferred-hydration 53.72 KiB -24 B (-0.04%) 50.37 KiB 160.89 KiB 47.67 KiB ███▇▃▃▃▁
solid-start.full 55.44 KiB -17 B (-0.03%) 55.31 KiB 169.24 KiB 48.87 KiB ▁▁▁▁███▆▆▆▆▁

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 20, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7455

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7455

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7455

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7455

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7455

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7455

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7455

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7455

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7455

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7455

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7455

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7455

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7455

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7455

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7455

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7455

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7455

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7455

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7455

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7455

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7455

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7455

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7455

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7455

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7455

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7455

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7455

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7455

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7455

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7455

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7455

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7455

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7455

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7455

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7455

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7455

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7455

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7455

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7455

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7455

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7455

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7455

commit: 1cd3483

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 20, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing perf-lru-cache (1cd3483) with main (7df0d02)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant