Skip to content

Speed up Contains for 32-bit and 64-bit bitmaps - #587

Merged
lemire merged 2 commits into
masterfrom
contains-speedup
Nov 12, 2026
Merged

lemire merged 2 commits into
masterfrom
contains-speedup

Conversation

@lemire

@lemire lemire commented Nov 4, 2026

Copy link
Copy Markdown
Member

This PR makes Contains faster in both the root package and roaring64.

Changes

  • Type switch in Bitmap.Contains instead of a dynamic interface call. This also lets the bitmap-container bit test inline.
  • Dedicated runContainer16.contains: a branchless search over interval starts, then a single key - start <= length check. Before, it went through search → searchRange, which returns three values and counts comparisons.
  • Lower-bound key and array search (searchUint16, plus a uint32 version in roaring64): no early exit on equality (rarely taken, and it costs an extra branch per step), finished by a linear scan of the last 16 elements. It's used for the key lookup in Contains and in arrayContainer.contains. Other callers of binarySearch are unchanged.
  • New benchmarks: BenchmarkContains and BenchmarkContains64.

Results

Median ns/query, 4096 random queries (half hits, half misses), with the variants measured side by side in the same process. Hardware: Apple M4 Max (arm64) and Xeon Gold 6548N (amd64, pinned with taskset).

case M4 Max Xeon
run containers (16 or 1000 runs) −63% to −70% −69% to −70%
bitmap containers, 64 keys −13% −30%
array containers (64 or 4000 values) −15% to −17% −14% to −15%
many sparse keys (64 or 4096) −15% to −18% −4% to −10%
mixed, 1024 containers −18% −24%
roaring64 key search (8 to 16384 keys) −3% to −18% −5% to −18%

No case was slower on either machine.

Not included

A fully branchless key and array search is another 30–50% faster on the Xeon but 50–70% slower on the M4. Go won't emit CMOV/CSEL for a value carried through the loop, so the branchless loop has to wait for each load in turn. That could be a follow-up in an amd64-only file with a generic fallback, after measuring on a Neoverse arm64 server.

go test ./... passes.

- Bitmap.Contains uses a type switch instead of an interface call,
  which also lets the bitmap-container test inline.
- runContainer16.contains uses a dedicated branchless search over the
  interval starts instead of the general searchRange.
- Key lookup in Contains (both packages) and arrayContainer.contains
  use a lower-bound search (no early exit on equality) finished by a
  linear scan.
- Add BenchmarkContains and BenchmarkContains64.
@lemire
lemire merged commit edf34d5 into master Nov 12, 2026
14 checks passed
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