|
5 | 5 |
|
6 | 6 | DryDB is a read-only embedded B+Tree based key/value database, implemented pure C#. |
7 | 7 |
|
8 | | -``` |
9 | | -| Method | Mean | Error | StdDev | |
10 | | -|------------------- |------------:|----------:|----------:| |
11 | | -| DryDB_FindByKey | 37.57 us | 0.230 us | 0.120 us | |
12 | | -| CsSqlite_FindByKey | 4,322.48 us | 44.492 us | 26.476 us | |
13 | | -``` |
| 8 | +<picture> |
| 9 | + <source media="(prefers-color-scheme: dark)" srcset="docs/benchmarks/point_lookup_dark.svg"> |
| 10 | + <img alt="Point lookup benchmark: DryDB 29 ns, RocksDB 241 ns, SQLite (prepared + immutable) 943 ns, SQLite (default) 6,656 ns per query" src="docs/benchmarks/point_lookup_light.svg"> |
| 11 | +</picture> |
| 12 | + |
| 13 | +See [Performance](#performance) for details. |
14 | 14 |
|
15 | 15 | ## Features |
16 | 16 |
|
@@ -44,6 +44,64 @@ DryDB is a read-only embedded B+Tree based key/value database, implemented pure |
44 | 44 | - CLI tool |
45 | 45 | - Support for large BLOBs. (Values exceeding 65,536 bytes are stored on a separated page.) |
46 | 46 |
|
| 47 | +## Performance |
| 48 | + |
| 49 | +All benchmarks read a table of 10,000 rows (int64 primary key, 13-byte value) with 4 KB pages, on Apple M-series / .NET 10, measured with BenchmarkDotNet. Lower is better. |
| 50 | + |
| 51 | +Two SQLite configurations are shown to keep the comparison fair: |
| 52 | + |
| 53 | +- **default** — a command is created and parsed for every query (typical naive usage) |
| 54 | +- **prepared + immutable** — the statement is prepared once and reused, and the file is opened with [`immutable=1`](https://www.sqlite.org/uri.html#uriimmutable), the fastest read-only setup SQLite offers |
| 55 | + |
| 56 | +### Point lookup |
| 57 | + |
| 58 | +<picture> |
| 59 | + <source media="(prefers-color-scheme: dark)" srcset="docs/benchmarks/point_lookup_dark.svg"> |
| 60 | + <img alt="Point lookup benchmark: DryDB 29 ns, RocksDB 241 ns, SQLite (prepared + immutable) 943 ns, SQLite (default) 6,656 ns per query" src="docs/benchmarks/point_lookup_light.svg"> |
| 61 | +</picture> |
| 62 | + |
| 63 | +### Range scan |
| 64 | + |
| 65 | +<picture> |
| 66 | + <source media="(prefers-color-scheme: dark)" srcset="docs/benchmarks/range_scan_dark.svg"> |
| 67 | + <img alt="Range scan benchmark (100 rows): DryDB 1.4 µs, SQLite (prepared + immutable) 9.6 µs, RocksDB 14.0 µs, SQLite (default) 17.4 µs per query" src="docs/benchmarks/range_scan_light.svg"> |
| 68 | +</picture> |
| 69 | + |
| 70 | +### Count by key range |
| 71 | + |
| 72 | +<picture> |
| 73 | + <source media="(prefers-color-scheme: dark)" srcset="docs/benchmarks/count_range_dark.svg"> |
| 74 | + <img alt="Count benchmark (8,000 rows): DryDB 15.2 µs, SQLite (default) 108 µs, SQLite (prepared + immutable) 117 µs, RocksDB 982 µs per query" src="docs/benchmarks/count_range_light.svg"> |
| 75 | +</picture> |
| 76 | + |
| 77 | +<details> |
| 78 | +<summary>Raw BenchmarkDotNet results</summary> |
| 79 | + |
| 80 | +1 op = 1000 queries for point lookup, 100 queries for range scan and count. |
| 81 | + |
| 82 | +| Type | Method | Mean | Error | StdDev | Allocated | |
| 83 | +|--------------- |------------------------- |-------------:|-------------:|-------------:|-----------:| |
| 84 | +| ReadBenchmark | DryDB_FindByKey | 29.00 us | 2.133 us | 1.269 us | - | |
| 85 | +| ReadBenchmark | RocksDB_FindByKey | 240.74 us | 42.123 us | 27.862 us | 40032 B | |
| 86 | +| ReadBenchmark | CsSqlite_FindByKey_Fair | 943.04 us | 152.115 us | 100.615 us | 48000 B | |
| 87 | +| ReadBenchmark | CsSqlite_FindByKey | 6,655.59 us | 557.215 us | 368.563 us | 48000 B | |
| 88 | +| RangeBenchmark | DryDB_GetRange | 140.02 us | 6.377 us | 4.218 us | - | |
| 89 | +| RangeBenchmark | CsSqlite_GetRange_Fair | 958.48 us | 124.718 us | 82.493 us | 480000 B | |
| 90 | +| RangeBenchmark | RocksDB_GetRange | 1,396.66 us | 157.288 us | 104.036 us | 726432 B | |
| 91 | +| RangeBenchmark | CsSqlite_GetRange | 1,743.15 us | 218.851 us | 144.757 us | 480000 B | |
| 92 | +| CountBenchmark | DryDB_CountRange | 1,524.56 us | 123.628 us | 81.772 us | - | |
| 93 | +| CountBenchmark | CsSqlite_CountRange | 10,796.05 us | 956.138 us | 632.426 us | - | |
| 94 | +| CountBenchmark | CsSqlite_CountRange_Fair | 11,715.51 us | 926.636 us | 551.426 us | - | |
| 95 | +| CountBenchmark | RocksDB_CountRange | 98,221.60 us | 5,595.564 us | 3,701.119 us | 25606432 B | |
| 96 | + |
| 97 | +</details> |
| 98 | + |
| 99 | +> [!NOTE] |
| 100 | +> DryDB returns values as zero-copy slices of cached pages, so read paths allocate no managed memory. |
| 101 | +> The RocksDB numbers go through the C# binding ([rocksdb-sharp](https://github.com/curiosity-ai/rocksdb-sharp)), whose iterator allocates a `byte[]` per key/value access — the count benchmark in particular is dominated by that binding overhead rather than the storage engine itself. |
| 102 | +
|
| 103 | +The benchmark source is in [sandbox/DryDB.Benchmark](sandbox/DryDB.Benchmark). |
| 104 | + |
47 | 105 | ## Why read-only ? |
48 | 106 |
|
49 | 107 | ## Installation |
|
0 commit comments