Skip to main content
Mutuus Team6 min read

What Happens When Bitmap Boundaries Listen to the Data?

What if bitmap container boundaries adapted to data density instead of falling at fixed intervals? We benchmarked 14 operations at three scales to find out. The results surprised us in both directions.

organicsbenchmarksdiatom-bitmapbitmaps
Share

Research in progress

The data and performance claims on this page reflect ongoing research, implementation, and experimentation. They may change as benchmarks are rerun and formal papers are finalized.

Skim
Read
Deep Dive

We benchmarked Diatom Bitmap against Roaring Bitmap across 14 operations at three scales (1K, 10K, 100K values). Diatom wins decisively on aligned set operations at scale. Roaring wins on point queries and construction. This post presents every number.

In our earlier post, we described the limitations of fixed-boundary bitmap indexing: uniform 2^16 partitioning that ignores data topology, containers that don't know their own density, and no mechanism for thermal adaptation. We asked whether a density-aware bitmap could do better.

This post is the answer. We ran Criterion benchmarks on every core operation, comparing Diatom Bitmap against roaring-rs (the canonical Rust implementation of Roaring Bitmap). The results are not uniformly favorable. That's why they're worth reporting.

Set Operations at Scale

At 100K values with aligned boundaries, Diatom Bitmap outperforms Roaring on every set operation. AND intersection is 7.4x faster. Cardinality-only AND is 19.3x faster.

Set operations are the hot path for bitmap indexes in analytics engines. Every query in Apache Druid, Apache Pinot, or ClickHouse that combines filter predicates runs AND, OR, or ANDNOT across bitmap indexes.

Operation1K10K100K
AND~parity1.09x faster7.4x faster
OR3.2x slower~parity2.7x faster
ANDNOT1.2x slower1.05x faster6.0x faster
XOR1.1x slower1.22x faster2.9x faster
AND cardinality2.7x slower~parity19.3x faster

The pattern is consistent: Diatom has fixed overhead at small sizes (1K), reaches parity around 10K, and pulls ahead at 100K. The gap widens with scale.

At 100K, aligned AND is 232 ns versus 1,724 ns, or 7.4x faster.

At 100K, aligned OR is 732 ns versus 1,984 ns, or 2.7x faster.

At 100K, aligned AND cardinality is 62 ns versus 1,202 ns, or 19.3x faster.

The absolute numbers at 100K:

  • AND: Diatom 232 ns vs Roaring 1,724 ns
  • OR: Diatom 732 ns vs Roaring 1,984 ns
  • ANDNOT: Diatom 334 ns vs Roaring 2,003 ns
  • XOR: Diatom 686 ns vs Roaring 2,002 ns
  • AND cardinality: Diatom 62 ns vs Roaring 1,202 ns

The 19.3x result on cardinality-only AND deserves explanation. Three factors compound:

  1. Cached popcount. Diatom caches cardinality at the bitmap level. Roaring recalculates container cardinalities during intersection.
  2. Empty-container short-circuit. Aligned Diatom containers that are empty skip entirely. No iteration, no word operations, no popcount. At 100K with density-derived boundaries, most containers are empty after intersection.
  3. Fewer containers. Density-derived boundaries coalesce sequential data into fewer, larger containers than Roaring's fixed 2^16 partitioning. Fewer containers means fewer loop iterations at the set-operation level.

These results require aligned boundaries (both bitmaps sharing the same boundary structure). We address what happens without alignment in a later section.

Where Diatom Also Wins

Beyond set operations, Diatom stays faster on cardinality queries (1.9-2.7x) and serialization (1.8-8.5x). Iteration is now closer to parity: faster at 1K and 100K, slightly slower at 10K.

Cardinality is O(1) in Diatom (a cached value at the bitmap level) versus O(containers) in Roaring. The result stays in a tight sub-nanosecond band across the tested sizes:

At 100K, cached cardinality is 0.47 ns versus 1.25 ns, or 2.7x faster.

SizeDiatomRoaringRatio
1K0.46 ns0.87 ns1.9x faster
10K0.48 ns0.93 ns1.9x faster
100K0.47 ns1.25 ns2.7x faster

Iteration is no longer a decisive headline win, but it remains competitive. Bitmap containers iterate over u64 words using trailing-zeros extraction. Array containers iterate sorted u16 slices. In the current implementation that is enough to edge out Roaring at 1K and 100K, but not at 10K:

At 100K, iteration is 317,377 ns versus 357,463 ns, or 1.1x faster.

SizeDiatomRoaringRatio
1K2.8 us3.5 us1.3x faster
10K38.0 us34.2 us1.1x slower
100K317.4 us357.5 us1.1x faster

Serialization uses a compact binary format (magic: DBTM). At 10K, Diatom serializes 8.5x faster than Roaring:

At 10K, serialization is 211 ns versus 1,792 ns, or 8.5x faster.

SizeDiatomRoaringRatio
1K701 ns1,256 ns1.8x faster
10K211 ns1,792 ns8.5x faster

Where Diatom Loses

Point queries (contains) are still slower than Roaring, but the corrected 100K story is much narrower than the old draft implied. Construction (from_sorted) is still 3.7-5.8x slower. Insert stays slower on both sequential and sparse workloads. We publish losses alongside wins.

contains() hit path at 100K: 2.2x slower after the integrity fix. This remains the clearest standalone regression, but it is no longer the catastrophic 7.6x story the old draft carried. A March 20, 2026 investigation found that the earlier 100K result was being inflated by a boundary-range correctness bug. After fixing that path, the standalone contains() penalty remains real, but it is now closer to the cost you would expect from explicit boundary lookup plus in-container search.

On present values at 100K, Diatom takes 463,569 ns versus Roaring's 208,302 ns, or 2.2x slower.

SizeDiatomRoaringRatio
1K11.7 us8.9 us1.3x slower
10K35.2 us12.1 us2.9x slower
100K463.6 us208.3 us2.2x slower

The corrected 100K result still says the same qualitative thing: Roaring is better for standalone point lookups. It no longer supports the stronger claim that Diatom falls off a cliff at scale on this path.

Miss-path contains() is milder at 100K: 228,482 ns versus 138,958 ns, or 1.6x slower.

from_sorted(): 3.7-5.8x slower. Constructing a Diatom Bitmap from sorted values runs a density histogram followed by valley detection to place boundaries. Roaring just distributes values into fixed 2^16 containers. The boundary placement algorithm is the cost of adaptivity. It's O(n) but with a higher constant factor.

At 100K, bulk construction is 2,495,236 ns versus 429,623 ns, or 5.8x slower.

SizeDiatomRoaringRatio
1K15.4 us4.2 us3.7x slower
10K183.8 us44.5 us4.1x slower
100K2,495.2 us429.6 us5.8x slower

Insert: 1.8-2.3x slower. Each insert must locate the correct container via boundary lookup and potentially update density metadata. The overhead is constant per operation and doesn't grow disproportionately with scale.

These are real costs. Whether they are acceptable depends on your workload. If your hot path is set operations on aligned bitmap families, Diatom has a real case. If your hot path is one-off point lookups (contains) or bulk construction, Roaring is the better choice today.

Deserialization is no longer a weakness. Diatom still serializes 8.5x faster than Roaring at 10K, and after fixing the readback path it now deserializes 2.0x faster too: 412 ns versus 819 ns. The earlier asymmetry turned out to be partly a correctness bug in disguise: bitmap payloads were being rebuilt bit-by-bit on load, and compressed containers were not round-tripping correctly.

The 1K overhead story. At 1K values, Diatom still pays visible overhead on point queries and several set operations, but it is already competitive on cardinality, iteration, and serialization. The current picture is less “slow on everything small” and more “mixed at 1K, compelling only once the workload reaches aligned set operations or larger scales.” Diatom is still not designed for tiny standalone bitmaps.

The Scaling Story

Scale helps Diatom most when boundaries are aligned and the workload is dominated by set work. It does not erase the standalone costs.

Roaring uses fixed 2^16 boundaries. Every 65,536 consecutive values get one container. The container count scales linearly with value range, regardless of data distribution. Dense regions and sparse regions get the same treatment.

Diatom uses density-derived boundaries. The boundary placement algorithm runs a histogram over the value space and detects valleys in the density distribution. Dense regions get fewer, larger containers. Sparse regions get many small containers or none at all. At 100K sequential values, Diatom coalesces data into fewer containers than Roaring, with each container holding more values. Fewer containers means fewer loop iterations in set operations, fewer cache misses, and fewer function calls.

What scales is not a blanket “better bitmap” claim. What scales is the aligned set-operation path. At 1K, both approaches produce a handful of containers and Diatom's fixed overhead dominates. At 100K, aligned set work benefits from fewer effective containers and cached metadata. But standalone membership and construction still lose, and fixed-floor remains the honest standalone baseline inside Diatom itself.

The alignment requirement. These set operation numbers assume aligned boundaries, meaning both bitmaps share the same boundary structure. That is not a footnote. It is the current wedge.

Bitmaps in the same analytical domain should be built as a family and keep aligned morphology together. The current Symbiosis path supports shared-family construction, aligned operations, partner warming, and coordinated merge suppression. It does not yet justify a broad “adaptive wins everywhere” claim, but it does identify where Diatom is meaningfully different from a standalone fixed-boundary bitmap.

That is why the Diatom story has narrowed. The most credible use is coordinated bitmap families, not arbitrary standalone bitmaps dropped into Roaring's home turf.

What's Next

The next stage for Diatom is narrower than the first draft claimed: keep the truthful core, improve coordinated family maintenance, and prove whether shared morphology creates a decision-changing wedge.

We now know more precisely what the benchmark suite does and does not support. Diatom is not yet a general Roaring replacement. It is a more specific research primitive: adaptive morphology with a fixed-floor baseline and a plausible family-level wedge when related bitmaps share structure.

That is still worth studying. It is simply a narrower and more honest claim than the original draft.

Related Posts

7 min read

Nacre Array Benchmarks: The Numbers

We promised the full benchmark numbers. Here they are: every operation, every scale, wins and losses. Vec is still faster at some things. Nacre Array is faster at others. The crossover points tell the real story.

8 min read

Nacre Array: What If Your Vec Had a Spine?

Mother-of-pearl is 3,000 times tougher than the crystals it's made of. The secret is layered organization with flexible mortar between rigid segments. We built an array that works the same way.

10 min read

What Happens When Your Index Navigates by Smell?

Echo Field just ran its first away-field benchmark on 18,840 real arXiv sections. 100% recall parity with brute-force, 18x faster at 500K vectors, and churn-stable under continuous mutation. Here's what we found.

5 min read

What If Your Bitmap Knew Its Own Density?

Bitmap indexes power analytics queries across billions of rows. But the dominant implementation treats all data the same: fixed boundaries, uniform containers, no adaptation. What if the bitmap could see its own density distribution?

5 min read

Why Nature Builds Better Arrays

Every system that stores sequential data eventually hits the same wall: cold data costs the same as hot, inserts require shifting everything, and there's no way to split without copying. What if the structure itself knew the difference?

6 min read

Why Your Data Structure Doesn't Have a Metabolism

Data structures are benchmarked at birth, compared as static objects, and optimized for a single moment in time. But biological systems invest in costly metabolic machinery that pays off across a lifecycle. What if the overhead isn't the problem, but the investment?

Discussion