Skip to content

perf(util): add FixedBitSet.copyOf() fast paths for SparseLiveDocs and DenseLiveDocs#16282

Open
salvatorecampagna wants to merge 3 commits into
apache:mainfrom
salvatorecampagna:perf/fixedbitset-copyof-livedocs-fast-paths
Open

perf(util): add FixedBitSet.copyOf() fast paths for SparseLiveDocs and DenseLiveDocs#16282
salvatorecampagna wants to merge 3 commits into
apache:mainfrom
salvatorecampagna:perf/fixedbitset-copyof-livedocs-fast-paths

Conversation

@salvatorecampagna

@salvatorecampagna salvatorecampagna commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Make FixedBitSet.copyOf() 135x to 193x faster for DenseLiveDocs and 11x to 48x faster for SparseLiveDocs.

Summary

FixedBitSet.copyOf(Bits) has fast paths for FixedBitSet and FixedBits but not for SparseLiveDocs and DenseLiveDocs, the two LiveDocs types introduced in #15413. Both fall through to the generic O(maxDoc) per-bit loop. The hot caller is PendingDeletes.getMutableBits(), which invokes FixedBitSet.copyOf(liveDocs) on the first delete after a reader snapshot. Under write-heavy workloads this cost accumulates across open reader generations.

Each type now exposes a package-private toFixedBitSet() method that FixedBitSet.copyOf() delegates to, keeping the copy logic next to the data it knows about. DenseLiveDocs stores live docs in a FixedBitSet with identical semantics, so toFixedBitSet() clones it at O(maxDoc/64). SparseLiveDocs stores deleted positions in a SparseFixedBitSet, so toFixedBitSet() allocates the backing long[] prefilled with -1L (all live), masks off the ghost bits in the last word to satisfy FixedBitSet invariants, then clears only the deleted positions via nextSetBit at O(maxDoc/64 + deletedDocs).

Benchmarks

LiveDocsCopyOfBenchmark, FixedBitSet.copyOf() average time (us/op), -wi 5 -i 7 -f 3. Baseline = main HEAD; contender = this PR.

DenseLiveDocs

maxDoc del rate baseline (us) err% contender (us) err% speedup
1M 0.1% 317.838 +/- 5.686 1.8% 2.362 +/- 0.039 1.7% 135x
10M 0.1% 3166.801 +/- 91.964 2.9% 20.797 +/- 1.160 5.6% 152x
100M 0.1% 31434.474 +/- 234.936 0.7% 198.186 +/- 11.443 5.8% 159x
1M 1% 371.669 +/- 6.345 1.7% 2.302 +/- 0.054 2.3% 162x
10M 1% 3766.590 +/- 35.200 0.9% 19.476 +/- 0.684 3.5% 193x
100M 1% 37788.094 +/- 191.936 0.5% 203.131 +/- 10.855 5.3% 186x

SparseLiveDocs

maxDoc del rate baseline (us) err% contender (us) err% speedup
1M 0.1% 518.084 +/- 6.739 1.3% 10.900 +/- 0.096 0.9% 48x
10M 0.1% 4976.429 +/- 55.761 1.1% 111.454 +/- 1.495 1.3% 45x
100M 0.1% 49412.733 +/- 424.527 0.9% 1152.935 +/- 12.952 1.1% 43x
1M 1% 1201.822 +/- 43.661 3.6% 97.187 +/- 1.349 1.4% 12x
10M 1% 12062.162 +/- 146.840 1.2% 986.402 +/- 14.975 1.5% 12x
100M 1% 119511.553 +/- 688.099 0.6% 10884.173 +/- 159.084 1.5% 11x

The SparseLiveDocs speedup shrinks as the deletion rate grows: the contender always pays O(maxDoc/64) to fill the backing array, and on top of that clears one position per deleted document. At low deletion rates the fill dominates and the gap with the O(maxDoc) baseline is large; at higher rates the clearing loop contributes more and the advantage narrows.

FixedBitSet.copyOf(Bits) already has fast paths for FixedBitSet and
FixedBits, but SparseLiveDocs and DenseLiveDocs (introduced in apache#15413)
fell through to the O(maxDoc) generic loop.

Each type now exposes a package-private toFixedBitSet() method that
FixedBitSet.copyOf() delegates to:

- DenseLiveDocs stores live docs in a FixedBitSet: clone it directly,
  O(maxDoc/64).
- SparseLiveDocs stores deleted docs in a SparseFixedBitSet: pre-fill
  the backing long[] with -1L and clear only deleted positions using
  nextSetBit, O(deletedDocs + maxDoc/64).

The hot caller is PendingDeletes.getMutableBits(), which invokes
copyOf(liveDocs) on the first delete after a snapshot.
@github-actions github-actions Bot added this to the 10.6.0 milestone Jun 22, 2026
@salvatorecampagna salvatorecampagna marked this pull request as ready for review June 22, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants