Skip to content

perf(bitmap): grow a large buffer by a fraction instead of doubling it - #60

Merged
aliszka merged 2 commits into
mainfrom
feat/grow-formula
Sep 23, 2026
Merged

aliszka merged 2 commits into
mainfrom
feat/grow-formula

Conversation

@aliszka

@aliszka aliszka commented Sep 22, 2026

Copy link
Copy Markdown
Member

Both of sroar's reallocation sites grew a buffer by its own capacity, which is right for a small bitmap and wrong for a large one. This puts one shared grownCap behind both, keeps the old rule below a size limit and takes a fraction of the grown size above it. It also exports capInBytes as CapInBytes.

Motivation

A bitmap grown by repeated Or of monotone doc IDs — weaviate's in-memory roaringset range representation, and any accumulator built that way — ends up carrying close to a second copy of itself in slack. That slack is resident for as long as the process runs, while the copying that avoids it is a one-off at build time.

Approach

Below growDoubleLimit the arithmetic is byte-identical to the old cap + max(cap, need), which is what lets the package's existing capacity assertions stand unchanged.

The limit branches on the old capacity, not on the grown size, so a fresh bitmap absorbing one large Or still lands on an exact fit. That is deliberate: roaringset.Condense is exactly that shape and its doc comment says the minimal size is the point. A bitmap that keeps accumulating instead pays one copy for it at the next merge.

The slack has no floor. An earlier form floored it at the limit so the handoff from doubling stayed continuous, which made every bitmap just past the limit carry a limit-sized slack and turned the band above it into arithmetic growth. Dropping the floor costs monotonicity of grownCap in cp — a buffer just under the limit grows larger than one just over it — which is harmless for a single buffer, whose capacity only ever increases.

Key areas for review

  • bitmap.go grownCapWith — the two arms and the step down at the limit
  • bitmap_opt.go expandConditionally — check the new capacity still covers ln+sizeKeys+sizeContainers
  • bitmap_test.go TestGrownCap — the rows either side of the limit are what pin the discontinuity

Risks

  • Building a large bitmap copies more. That is a one-off against slack that stays resident, and BenchmarkGrownCap sweeps both constants so the balance can be re-decided rather than re-derived.
  • roaringset/binary_search_tree.go documents that sroar grows by doubling, and its heap-ratio test bounds slack accordingly. Every shape that test builds stays below the limit, so the bounds hold; the prose wants fixing when weaviate bumps sroar.

Testing

TestGrownCap covers both arms, the boundary and the exact-fit case. TestGrowthKeepsSlack and TestGrowthKeepsSlackOnSet drive the two growth sites end to end through Or and Set; both fail against the old formula. The package's existing capacity assertions are unchanged.

Unrelated, found while reviewing: FillUp computes requiredContainersCount * maxContainerSize in int, which wraps on a 32-bit build. Recorded in the findings inbox, not fixed here.

aliszka and others added 2 commits September 22, 2026 15:24
Callers outside the package can see only the part of a bitmap's buffer
that LenInBytes counts, not the allocation behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both reallocation sites grew a buffer by its own capacity, so a bitmap
of tens of megabytes carried a second copy of itself in slack it would
never fill, for as long as the process ran. They now share grownCap,
which keeps doubling while the buffer is small and adds a fraction of
the grown size past a limit. Below the limit the arithmetic is
unchanged, which is what the existing capacity assertions encode.

It costs copy work: building a large bitmap copies several times the
final size where doubling copied about one. That is a one-off at build
time against slack that stays resident. BenchmarkGrownCap sweeps the
limit and the fraction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@orca-security-eu orca-security-eu Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Infrastructure as Code high 0   medium 0   low 0   info 0 View in Orca
Passed Passed SAST high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Vulnerabilities high 0   medium 0   low 0   info 0 View in Orca

@aliszka
aliszka merged commit 194dd5b into main Sep 23, 2026
6 checks passed
@aliszka
aliszka deleted the feat/grow-formula branch September 23, 2026 11:02
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.

2 participants