Skip to content

docs: benchmark report, refreshed results and README - #89

Merged
venkat1701 merged 16 commits into
mainfrom
docs/readme-benchmark-report
Oct 14, 2026
Merged

venkat1701 merged 16 commits into
mainfrom
docs/readme-benchmark-report

Conversation

@venkat1701

@venkat1701 venkat1701 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Why

The README and docs/benchmarks.md still showed results from before the last six PRs, with unequal caches. This adds a full benchmark report as a PDF, refreshes the published results from main, and updates the README.

What changes

  • Sync fix in the HyperGraphDB adapter. HyperGraphDB 1.4 begins every JE transaction with WRITE_NO_SYNC, which overrides the COMMIT_SYNC the adapter set on the environment. Its "sync" commits took about 0.1 ms while one forced write on this machine takes about 3.8 ms, so they were never synced. The adapter now calls Environment.flushLog(true) after each commit in sync mode. The sync tables published before this measured HyperGraphDB without any sync.

  • docs/benchmark-report.pdf, built from benchmarks/report:

    • the two systems, the dataset, the 26 workloads and the method;
    • results and discussion for three configurations, and threats to validity;
    • appendices with every table, every run and the steps to reproduce.

    gen.py builds every table, figure and quoted number from the raw JSON in benchmarks/report/results, so nothing in it is typed by hand.

  • docs/benchmarks.md has the new results, a rewritten analysis, and a setup section that explains the sync fix.

  • README describes logged commits and compaction, and has a short performance table that links to the report.

  • DatasetStats prints the dataset size and skew used in the report.

Results in short

This branch includes #90, and the numbers are from that code (9291e18), so #90 should merge first. Both caches at 64 MiB, scale 1 with 5 runs per store in each durability mode, and scale 4 with 3 runs, the two engines alternating.

  • Faster in HStore:
    • incidence sets 4.2 to 11x;
    • co-membership 11 to 20x;
    • batched updates 5.4 to 33x;
    • member enumeration 3.2x at scale 4 (2.3x at scale 1, ranges overlapping), and two-hop queries 3.3 to 5.5x;
    • single commits under async: 659 B written against 6.9 KiB.
  • Smaller on disk in HStore: 3.5 to 14x after loading.
  • Faster in HyperGraphDB: opening a store after a crash at scale 1, 76 ms against 128 ms (0.59x). At scale 4 HStore is faster, 219 ms against 455 ms.
  • Too close to call: single commits at scale 1. Under async HyperGraphDB is slightly ahead at the median (0.88x, ranges overlapping), and under sync both are bound by the forced write (1.09x). No acknowledged commit was lost in any run.

The laptop was busy: the load average was 3.4 to 18.3 on 10 cores. The report marks every ratio whose run ranges overlap. The ratios at scale 1 are smaller than in the first version of this PR, mostly because HyperGraphDB ran faster in this quieter window: between the two sets of runs, its scale 1 throughput rose 1.3 to 3.3 times per workload, and HStore's 0.8 to 1.8 times. Earlier alternated runs of main and #90 at scale 1 showed no slowdown in HStore.

Testing

…ability

HyperGraphDB 1.4 begins every JE transaction with WRITE_NO_SYNC, which
overrides the COMMIT_SYNC the adapter set on the environment. In sync mode
its commits were never forced: single-update commits took about 0.1 ms
while one forced write on the test machine takes about 3.8 ms. The adapter
now calls Environment.flushLog(true) after every commit in sync mode.
DatasetStats prints node, hyperedge and incidence counts, the cardinality
distribution and the incidence-set size distribution at scales 1 and 4,
for the dataset table in the benchmark report.
Replaces the tables with runs of commit 459b8be at scale 1 (async and sync)
and scale 4, both caches at 64 MiB. The analysis now covers where
HyperGraphDB leads (member enumeration once the data outgrows HStore's
node cache), the results that are too close to call, and what HStore
spends. The setup explains the sync fix for HyperGraphDB and notes that
sync results published before it measured HyperGraphDB without syncing.
The report describes both systems, the dataset, workloads and method,
presents the results for three configurations, discusses them, and lists
the threats to validity. Appendices hold every table, every run and the
steps to reproduce. gen.py builds all tables, figures and quoted numbers
from the raw results in benchmarks/report/results.
Decoding a leaf used to work out the encoded size of every key and value
straight away, for inline member lists inside catalog records too. Only
writes read those sizes. A frozen leaf now measures itself the first time
bytes() or heapBytes() is called, and writable leaves still measure when
they are created.
Codecs that write each value without reference to the others (the catalog,
posting lists and row codecs) now say so with independentValues(), and
their leaf pages put the length of every value in front of it. A reader
can then find one record without decoding the ones before it. Entry sizes
count the length, so leaves never overflow a page.

Storage format goes to 8. The history compaction test sat right at the
50% live ratio, and one byte per record pushed its early segment over, so
it now compacts below 75%.
Loading a catalog or posting-list leaf used to decode every record on the
page, with every inline member list, to answer a lookup for one of them.
A leaf whose records are framed by length now keeps the page bytes and the
offset of each record, and decodes a record on first read. The decoded
record is kept in the leaf, published with a compare-and-set so readers on
other threads see it whole, and charged to the node cache entry that holds
the leaf, so the cache still evicts to its budget as records unpack.
Charges for an entry that was evicted or cleared are dropped.

A packed leaf costs its page image plus its keys and offsets until records
are read, so far more of the catalog fits in a given node cache.
Decoding a leaf compared the header's refers-to-pages flag with its values,
which a packed leaf can't do without decoding every record. The packed leaf
now keeps the flag, and TreeVerifier, which decodes every record anyway,
compares the two, so hstore check still catches a header that disagrees
with its contents.
A writer copying a packed leaf, or measuring it, read every record through
value(), which kept each decoded record in the frozen leaf and charged the
node cache for it, one shard lock per record, for a leaf the write was
about to replace. Both now decode records straight into the copy or the
measurement and leave the packed leaf as it was.
The latency tables show the median of each run's maximum. The report also needs the single slowest operation across all runs, so gen.py now defines it for every latency workload.
All three configurations ran again on commit 9291e18, with both engines alternating in the same window. HStore now lists a hyperedge's members 3.2x faster than HyperGraphDB at scale 4, where it had been 0.13x, and two-hop queries are 4.4x faster.

The text follows the new runs. HyperGraphDB opens a crashed store faster at scale 1, the mixed writer kept its schedule in both engines under sync, and JE wrote 497 KiB per synced single commit. The discussion explains why decoding one record per lookup keeps the catalog in a 64 MiB cache.
The results tables come from the same runs as the report. The analysis now reflects scale 4, where HStore leads on member enumeration, and scale 1, where HyperGraphDB recovers from a crash faster. A new section compares allocation and bytes read per lookup before and after the change.
@venkat1701
venkat1701 changed the base branch from main to perf/lazy-leaves October 13, 2026 01:13
@venkat1701
venkat1701 changed the base branch from perf/lazy-leaves to main October 14, 2026 04:48
@venkat1701
venkat1701 merged commit 0c847f7 into main Oct 14, 2026
@venkat1701
venkat1701 deleted the docs/readme-benchmark-report branch October 14, 2026 04:48
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