Skip to content

Add index benchmarks against a generated 50k-row indexed table#302

Merged
chrisrichards merged 3 commits into
mainfrom
feature/index-benchmarks
Jul 6, 2026
Merged

Add index benchmarks against a generated 50k-row indexed table#302
chrisrichards merged 3 commits into
mainfrom
feature/index-benchmarks

Conversation

@chrisrichards

@chrisrichards chrisrichards commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Adds benchmarks demonstrating what automatic index use (v2.0.0–v2.1.0) buys, restores the historical cross-library comparison, and makes the benchmarked DbfDataReader package version switchable — everything measured against published packages (the project keeps its benchmark-the-release philosophy).

Cross-library comparison (v1.1.0 and v2.1.0 sections in benchmarks.md)

Recreates the Sylvan / NDbf / DbfDataReader comparison in the style of MarkPflug/Benchmarks — same method names as the old v0.5.x tables — reading every field of every record of the tl_2019_01_place census fixture. The package version is an MSBuild property, so any release is one flag away:

dotnet run -c Release --project test/DbfDataReader.Benchmarks -p:DbfDataReaderVersion=1.1.0 -- --filter "*DbfDataReaderBenchmarks*"

Findings: the sequential read path is unchanged from 1.1.0 to 2.1.0 (~1.15× NDbf, identical allocations) — the whole SQL/typed-query/index feature set is additive — and both improve markedly on the 0.5.x-era ratios (1.8–1.9× with 4× allocations). The 2.x-only index benchmarks are excluded from compilation when benchmarking 1.x. A project README documents the commands.

Index benchmarks

The repository fixtures are 3–16 rows — useless for index benchmarks — so BenchmarkTableGenerator writes a 50,000-row DBF plus a matching compound index: the CDX writer produces the simplest structure the library's strict reader accepts (uncompressed leaf entries, max-key interior entries, sibling-linked levels, a two-tag directory), with tags on ID (unique integers, exercising the #298 key transform) and CODE (character keys, ~100 rows per value). Before anything is measured, GlobalSetup verifies through the public API that indexed and scanned executions return identical rows for every benchmarked shape — a wrong writer fails the run rather than producing pretty-but-false numbers.

Results (Apple M3 Max, .NET 10, ShortRun; full tables in benchmarks.md)

Scenario Full scan Index Speed-up Allocations
Equality seek (1 of 50k rows) 23.0 ms 50 µs ~459× 17.9 MB → 32 KB
Range scan (100 rows) 23.8 ms 102 µs ~233× 17.9 MB → 81 KB
Character seek (100 rows) 22.2 ms 157 µs ~141× 16.8 MB → 81 KB
top 10 … order by ID desc 60.6 ms 3.0 ms ~20× 66.4 MB → 5.7 MB
count(*) filtered (10k rows) 23.6 ms 973 µs ~24× 18.0 MB → 1.2 MB
count(*) all rows 21.8 ms (read rows) 15.1 ms (status scan) 1.4× 16.8 MB → 10 KB

The last row is the honest one: a whole-table count still reads every record, so the win is modest in time but ~1,600× in allocations — value parsing is what it skips.

🤖 Generated with Claude Code

chrisrichards and others added 3 commits July 6, 2026 16:37
The repository fixtures are a handful of rows, so index benchmarks
need generated data: BenchmarkTableGenerator writes a 50,000-row DBF
plus a matching compound index (two tags: unique integer IDs and
duplicated character codes) using the simplest structure the
library's strict CDX reader accepts - uncompressed leaf entries,
max-key interior entries, and a two-tag directory. Generated files
are verified for index/scan result equivalence through the public
API before anything is measured.

IndexQueryBenchmarks pairs the same SQL with UseIndexes on and off:
equality seek 459x faster (50us vs 23ms), range scan 233x, character
seek 141x, top-10 descending order 20x, filtered COUNT(*) 24x, and
the whole-table COUNT(*) status scan cuts allocations from 16.8MB to
10KB. Results recorded in benchmarks.md.

The benchmarks project now references the published DbfDataReader
2.1.0 package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
….0 results

Recreates the Sylvan / NDbf / DbfDataReader comparison in the style
of MarkPflug/Benchmarks (reading every field of every record of the
tl_2019_01_place census fixture), with the same method names as the
historical v0.5.x tables. The DbfDataReader package version is now
an MSBuild property (-p:DbfDataReaderVersion=x.y.z) so any release
can be benchmarked; the 2.x-only index benchmarks are excluded from
compilation for 1.x versions. A project README documents the
commands.

benchmarks.md gains v1.1.0 and v2.1.0 comparison sections: the
sequential read path is unchanged between the two (~1.15x NDbf,
identical allocations), confirming the SQL and index features are
additive, and both improve markedly on the v0.5.x-era ratios.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@chrisrichards chrisrichards merged commit 6b2b349 into main Jul 6, 2026
3 checks passed
@chrisrichards chrisrichards deleted the feature/index-benchmarks branch July 6, 2026 16:04
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