Skip to content

Speed up ranges, async reads and database open - #29

Merged
hadashiA merged 7 commits into
mainfrom
optimize-read-followups
Jul 12, 2026
Merged

Speed up ranges, async reads and database open#29
hadashiA merged 7 commits into
mainfrom
optimize-read-followups

Conversation

@hadashiA

Copy link
Copy Markdown
Owner

Summary

Follow-up read-path optimizations, all measurement-driven (BenchmarkDotNet, Apple M-series, 10k rows / 4 KB pages):

Case Before After
Open + first query 7,369 µs / 20.5 MB alloc 118.9 µs / 0.64 MB 62x / 32x less
GetRange (100 rows) 118.0 µs 43.1 µs 2.7x
GetAsync (point, cached) 87.7 µs 23.2 µs 3.8x — within 1.27x of sync
Get (point, sync) ~21 µs 18.3 µs

(1 op = 1000 point queries / 100 range queries.)

Retain range result pages once per leaf

RangeResult.Add retained the page per row; rows arrive leaf by leaf, so a 100-row scan paid ~200 interlocked ops on the same hot page. Retaining once per consecutive page run cuts that to one pair per page.

Sync completion fast path for GetAsync

Async lookups paid the async state machine even when every page was cached — plus an ArrayPool rent/return per call in the typed ReadOnlyTable.GetAsync overloads. GetAsync now attempts the whole walk against cached pages first (TreeWalker.TryGetFromCache), completing synchronously in the common case; the typed overloads encode the key on the stack and only fall back to the pooled-buffer async path on a cache miss.

Clamp page cache capacity by data length

The S3-FIFO eviction queues and ghost table were sized by CacheSize / PageSize (512k entries with the 2 GB default), preallocating ~20 MB per open regardless of database size. The capacity is now additionally bounded by the number of pages the file can possibly contain (length / 32). Startup latency drops 62x for small databases; the test suite got ~7x faster as a side effect (every test opens a database).

Benchmarks added

Async point lookup, random-key point lookup (defeats the branch predictor, unlike the fixed-key variants), and open + first query.

Note: branchless binary search was tried and rejected

Converting the node binary searches to conditional-select form made random-key lookups slower (35 → 46 µs): it trades branch mispredictions for a serialized load-address dependency chain, losing speculative load-ahead. Kept in history (a7d7127 + revert) as a measurement record.

All 73 tests pass.

🤖 Generated with Claude Code

@hadashiA
hadashiA merged commit 22edc20 into main Jul 12, 2026
1 check passed
@hadashiA
hadashiA deleted the optimize-read-followups branch July 12, 2026 07:01
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