vortex-rdflib · bench SPARQL over rdflib stores — comparative benchmark

One run of bench/run_bench.py: vortex-rdflib's VortexRdflibStore against rdflib, oxrdflib (Oxigraph), pycottas-rdflib (COTTAS) and rdflib-hdt (HDT), over a synthetic SPARQL set: lookups and scans, star and chain joins, FILTER / DISTINCT / ORDER BY / GROUP BY, and patterns that name a graph. The Vortex rows are all Dictionary layout, crossed over residency and secondary index (none, by-copy, by-reference).

rdflib's engine evaluates every store, so the store serving quad patterns is the only variable. Each label records where a query's bytes come from: in-mem stores live fully in memory, file stores perform file access reads per query. Every query matches by construction, and row counts are cross-checked across stores.

The dataset is composed of quads: every statement about a subject goes into one graph, so the union of the graphs forms a triple set that queries that name no graph see. Each store loads it as an rdflib Dataset whose default graph is that union. HDT and COTTAS cannot serve named graphs through rdflib — HDT's format has none, and pycottas' store does not expose the ones COTTAS files can hold — so those two rows load the flattened N-Triples and do not report measurements in the Named graphs panel.

LOAD

Source file to queryable store

Cold start: from the shared source file — .nq for the stores with named graphs, the flattened .nt for HDT and COTTAS — to a queryable store. Most libraries build their own format first — serialize_rdf (vortex-rdflib), rdf2cottas, hdt convert — then open it; rdflib and oxrdflib parse directly. Median of repeated full rebuilds. rdflib-hdt is read-only: it cannot write HDT, so its file is built by the Rust hdt crate's CLI and only opened by the library measured here — that row's build is another implementation's work, unlike every other row.

QUERY

SPARQL execution, per query

Median wall-clock time to execute and fully consume each query. Green marks the fastest store in each column; the grey multiplier is the slowdown vs that column's best. rdflib-hdt's own optimize_sparql() fast path is switched off: it answers two of these joins wrongly — chain-2 with 26 rows and optional with 152, against the 18 and 1 that rdflib and rdflib-hdt itself return without it. That row therefore runs on rdflib's engine, as every other row does.

MEMORY

Peak memory (RSS)

Each store's whole lifecycle runs in its own process — build, every query, result materialization — so this is a clean per-store peak resident-set size: the kernel high-water mark (VmHWM), not a sample. Lower is better.

SET

The query set

Anchor terms and thresholds come from the dataset generator's modular arithmetic, so every query matches at least one row. heavy queries touch the whole dataset and run with a lower iteration budget.

DATA

Every measurement

Every (query, store) measurement from this run. Click a column to sort.

Benchmark Fastest Slowest Median Mean Samples