Skip to content

Optimize read hot path and expand benchmarks - #27

Merged
hadashiA merged 8 commits into
mainfrom
claude/performance-optimization-1c59d4
Jul 11, 2026
Merged

Optimize read hot path and expand benchmarks#27
hadashiA merged 8 commits into
mainfrom
claude/performance-optimization-1c59d4

Conversation

@hadashiA

Copy link
Copy Markdown
Owner

Read hot-path optimization

Point lookup: 45.18 µs → ~25 µs per 1000 queries (≈1.8x), allocations 171.88 KB → 0.

  • Cache ReadOnlyTable instances in ReadOnlyDatabaseGetTable used to allocate a ReadOnlyTable + TreeWalker + Dictionary per call, which was all of the ~176 B/lookup allocation. Tables are now built once at open.
  • Devirtualize key comparison in B+Tree search (KeyCompare) — the binary-search inner loop paid an interface dispatch per comparison via IKeyEncoding.Compare. A ReferenceEquals guard on the built-in encodings inlines the compare; this also helps AOT targets (IL2CPP) where dynamic PGO never devirtualizes. Also rewrote LeafNodeReader.TryFindValue as a dedicated Equal-search loop (no per-iteration switch (op)).
  • Pin the root page in TreeWalker — every lookup touches the root, so keep one retained reference and skip the cache lookup + refcount round-trip for it. This also removes the root's refcount cache line as a multi-thread contention point.
  • PageCache micro-optimizations — cache IMemoryOwner.Memory in the entry (avoids a virtual call per access) and relax the S3-FIFO frequency bump to a single CAS attempt (frequency is an approximate heuristic).

Bug fix

  • Fix page over-release in CountRange — same patch as Fix page over-release in CountRange #26 (cherry-picked here so the count benchmark can run; merges cleanly in either order). The sibling-walk loop released the newly acquired page instead of the finished one, eventually disposing a page buffer that was still in the cache map, after which any traversal through that page spun forever.

Benchmarks

  • Added RocksDB and a fair read-only SQLite configuration (immutable=1 + prepared statement reuse — ~7x faster than the naive per-query command) as comparison targets.
  • Added range scan (100 rows) and count by key range (8,000 rows) cases alongside point lookup, restructured into ReadBenchmark / RangeBenchmark / CountBenchmark over a shared StoreBenchmarkBase.
  • Added an 8-thread parallel point-lookup benchmark to track refcount cache-line contention.

README

  • Replaced the stale benchmark table with light/dark-aware SVG charts (docs/benchmarks/), a new Performance section with raw BenchmarkDotNet tables, and fairness notes (SQLite configs, RocksDB binding overhead). Outlier bars are truncated with an axis-break glyph so the remaining bars stay readable.
Case DryDB RocksDB SQLite (prepared+immutable) SQLite (default)
Point lookup (per query) 29 ns 241 ns 943 ns 6,656 ns
Range scan, 100 rows 1.4 µs 14.0 µs 9.6 µs 17.4 µs
Count, 8,000 rows 15.2 µs 982 µs 117 µs 108 µs

All 71 tests pass.

🤖 Generated with Claude Code

@hadashiA
hadashiA merged commit c6a1441 into main Jul 11, 2026
1 check passed
@hadashiA
hadashiA deleted the claude/performance-optimization-1c59d4 branch July 11, 2026 08:31
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