Repository navigation
perf(xor): speed up membership queries and filter construction - #324
Conversation
XOR benchmark resultsDataSketches has lower measured medians in most comparisons. Matched-seed construction and independent-filter batch construction are ahead of xorf in both profiles. Queries are ahead in the ordinary bench run; with LTO, imported-filter queries are close to parity. Each profile covers 69 scenarios and 90 comparisons: some scenarios test two DataSketches entry points. Counts below compare medians, not statistical significance.
Example: batch-building 32 Xor8 filters with 16K keys/filter takes 151 ms vs xorf’s 359 ms (ordinary) and 151 ms vs 352 ms (LTO). Ordinary-profile time before this PR was 294 ms for the same workload. What changed: inline queries across crates, expose the validated 32-bit index bound, and avoid short-circuiting the empty-filter check; replace conditional construction-queue enqueues with branchless updates, using three extra scratch slots. Remaining gaps:
Optimized Cargo bench builds throughout: ordinary 5 repeats, fat LTO / one codegen unit 7 repeats. Apple M4 Max, macOS arm64, rustc 1.101.0-nightly; xorf 0.13.0; code a683250. These are microbenchmarks, not ScopeDB end-to-end results. Delta vs xorf: negative = less time, positive = more time; this compares libraries, not before/after revisions. Remaining gaps: exact cases and measured rangesDefault bench: every median above xorf
Fat LTO, 1 codegen unit: every median above xorf
Reproduction commands and workload definitionsEnvironment: Apple M4 Max, 36 GiB RAM, macOS 26.6.2 (arm64); rustc 1.101.0-nightly (8006091eb 2026-10-17), LLVM 23.1.4; xorf 0.13.0. Both profiles are optimized Cargo bench builds. The LTO run explicitly sets fat LTO and one codegen unit. cargo x bench -- xor --repeat 5 --raw
CARGO_PROFILE_BENCH_LTO=fat CARGO_PROFILE_BENCH_CODEGEN_UNITS=1 cargo x bench -- xor --repeat 7 --rawNo round overrides or calibration were used. Each repetition includes fixture construction, input preparation, allocations, validation, result destruction, and cleanup. Peers use the same rounds and input parameters, are interleaved, and rotate execution order. Tables retain every sample in measurement order, including first runs and outliers. Times are integer milliseconds for the complete fixed workload, not per-operation latency; different scenario rows may have different work sizes. Every percentage below uses xorf as the denominator: Workload definitions
The original workloads retain their round counts; the two new groups were sized against library revision Complete results: both profiles, all 318 implementation rows and 1,908 raw samplesAll measurementsEach section gives the full scenario prefix; append the Case column to obtain the runner's exact scenario name. All implementation rows and raw samples are included. Default bench — 5 repeatsxor::build:: — 18 scenarios, 54 implementation rows
xor::build_batches:: — 3 scenarios, 9 implementation rows
xor::matched_build:: — 24 scenarios, 48 implementation rows
xor::query:: — 12 scenarios, 24 implementation rows
xor::query_shards:: — 12 scenarios, 24 implementation rows
Fat LTO, 1 codegen unit — 7 repeatsxor::build:: — 18 scenarios, 54 implementation rows
xor::build_batches:: — 3 scenarios, 9 implementation rows
xor::matched_build:: — 24 scenarios, 48 implementation rows
xor::query:: — 12 scenarios, 24 implementation rows
xor::query_shards:: — 12 scenarios, 24 implementation rows
|
XOR membership queries were slower than xorf 0.13.0, and constructing filters over independent input sets spent substantial time in conditional singleton enqueues. This change improves both paths and adds benchmarks for batches of independent builds and queries rotating across imported filters.
contains_hashacross crate boundaries and expose the validated 32-bit segment-length bound to the index reductions.u32slots to construction scratch space.Public APIs, construction seed selection, and serialization remain compatible. Known-empty filters still return false, but now perform the fingerprint probe before applying the empty mask. Imported filters with unknown cardinality retain the raw fingerprint comparison.
Measurements
The complete benchmark report includes all 69 scenarios in both the default bench profile (five repetitions) and fat LTO (seven repetitions), every raw sample, and all remaining gaps against xorf. In the complete LTO run, six query medians were 0.4%–2.2% higher than xorf, with overlapping observed ranges.
Apple M4 Max, macOS arm64; rustc 1.101.0-nightly (8006091eb 2026-10-17). All measurements use optimized Cargo bench builds and include fixture construction and cleanup.
The full XOR matrix covers 69 scenarios and 159 scenario/implementation combinations, with five repetitions each. The following default-profile build results compare the library at
33ec5c9with this change using identical benchmark definitions and rounds. Each round builds 32 independent Xor8 filters; times are medians in milliseconds.All 24 single-filter and imported-filter query scenarios had lower measured medians than xorf in the default bench profile.
A separate Xor8 run uses fat LTO and one codegen unit, with seven repetitions for each of 25 scenarios. Imported-filter queries use identical payloads and runtime width dispatch on both sides. Each query round performs 256 lookups on each of 32 filters.
These query results indicate approximate parity under LTO, not a statistically established speedup. Results are library microbenchmarks on one architecture.
The default-seed single-set 16,384-key build remains slower: base seed 0 needs two attempts for that input, while xorf's base seed 1 needs one. The deduplicating entry point also remains slower than xorf's distinct-input construction. Both cases remain in the benchmark matrix.
Reproduce with:
Validation
cargo x checkcargo x test: 817 tests passedcargo x lint: passed on an identical clean source copycargo x bench -- --test: 312 scenario/implementation combinations passedcargo +1.86.0 check --package benchmarks --all-targets --lockedcargo x bench --no-run