From 9a47dab78020e2df9ad8df03d40b18f9b1e89f57 Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Mon, 16 Sep 2024 17:43:33 +1000 Subject: [PATCH 1/2] Fix really slow BSI.ClearValues --- roaring64/bsi64.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/roaring64/bsi64.go b/roaring64/bsi64.go index 5d6019db..812ee4c7 100644 --- a/roaring64/bsi64.go +++ b/roaring64/bsi64.go @@ -973,21 +973,10 @@ func ClearBits(foundSet, target *Bitmap) { // ClearValues removes the values found in foundSet func (b *BSI) ClearValues(foundSet *Bitmap) { - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - b.eBM.AndNot(foundSet) - }() - for i := 0; i < b.BitCount(); i++ { - wg.Add(1) - go func(j int) { - defer wg.Done() - b.bA[j].AndNot(foundSet) - }(i) + b.eBM.AndNot(foundSet) + for i := range b.bA { + b.bA[i].AndNot(foundSet) } - wg.Wait() } // NewBSIRetainSet - Construct a new BSI from a clone of existing BSI, retain only values contained in foundSet From e5faa9e54325d828a5aa2e745c73f92b0a7962da Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Mon, 9 Mar 2026 21:35:13 +1100 Subject: [PATCH 2/2] Document why ClearValues is serial The goroutine-per-bit-plane approach it replaced was slower in practice. Goroutine creation overhead dominates for typical BSI sizes, and the cost compounds when ClearValues is called once per term in a deletion pass over an entire index (the pattern used in caterwaul's DeleteGroups). --- roaring64/bsi64.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/roaring64/bsi64.go b/roaring64/bsi64.go index 812ee4c7..484dd8ea 100644 --- a/roaring64/bsi64.go +++ b/roaring64/bsi64.go @@ -971,7 +971,13 @@ func ClearBits(foundSet, target *Bitmap) { target.AndNot(foundSet) } -// ClearValues removes the values found in foundSet +// ClearValues removes from the BSI all values whose column IDs are in +// foundSet, modifying the BSI in place. +// +// The implementation is intentionally serial. A previous goroutine-per-bit-plane +// approach was slower in practice: goroutine creation overhead dominated for +// typical BSI sizes, and the cost compounds when ClearValues is called in a +// tight loop (e.g. once per term across an entire index during a deletion pass). func (b *BSI) ClearValues(foundSet *Bitmap) { b.eBM.AndNot(foundSet) for i := range b.bA {