diff --git a/arraycontainer.go b/arraycontainer.go index a5c28717..ae09a1a0 100644 --- a/arraycontainer.go +++ b/arraycontainer.go @@ -628,6 +628,30 @@ func (ac *arrayContainer) xor(a container) container { panic("unsupported container type") } +func (ac *arrayContainer) ixor(a container) container { + switch x := a.(type) { + case *arrayContainer: + return ac.ixorArray(x) + case *bitmapContainer: + return ac.ixorBitmap(x) + case *runContainer16: + return ac.ixorRun16(x) + } + panic("unsupported container type") +} + +func (ac *arrayContainer) ixorArray(value2 *arrayContainer) container { + return ac.xorArray(value2) +} + +func (ac *arrayContainer) ixorBitmap(value2 *bitmapContainer) container { + return value2.ixor(ac) +} + +func (ac *arrayContainer) ixorRun16(value2 *runContainer16) container { + return value2.ixor(ac) +} + func (ac *arrayContainer) xorArray(value2 *arrayContainer) container { value1 := ac totalCardinality := value1.getCardinality() + value2.getCardinality() diff --git a/benchmark_test.go b/benchmark_test.go index d54d86e0..094cf1c6 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -1067,6 +1067,35 @@ func BenchmarkXorLopsided(b *testing.B) { } } +// BenchmarkXorDense benchmarks in-place Xor (ixor) on dense data +// where containers are bitmapContainers (>4096 values per 16-bit chunk). +// This is where in-place mutation of the bitmap[]uint64 slice should shine. +func BenchmarkXorDense(b *testing.B) { + b.StopTimer() + r := rand.New(rand.NewSource(0)) + // 50 chunks, each with ~10000 values → bitmapContainers (threshold is 4096) + numChunks := 50 + valsPerChunk := 10000 + s := NewBitmap() + for chunk := 0; chunk < numChunks; chunk++ { + base := uint32(chunk) * 65536 + for i := 0; i < valsPerChunk; i++ { + s.Add(base + uint32(r.Intn(65536))) + } + } + x2 := NewBitmap() + for chunk := 0; chunk < numChunks; chunk++ { + base := uint32(chunk) * 65536 + for i := 0; i < valsPerChunk; i++ { + x2.Add(base + uint32(r.Intn(65536))) + } + } + b.StartTimer() + for j := 0; j < b.N; j++ { + s.Xor(x2) + } +} + func BenchmarkBitmapReuseWithoutClear(b *testing.B) { for j := 0; j < b.N; j++ { s := NewBitmap() diff --git a/bitmapcontainer.go b/bitmapcontainer.go index 89682092..f4ea79f3 100644 --- a/bitmapcontainer.go +++ b/bitmapcontainer.go @@ -915,6 +915,43 @@ func (bc *bitmapContainer) iandBitmap(value2 *bitmapContainer) container { return bc } +func (bc *bitmapContainer) ixor(a container) container { + switch x := a.(type) { + case *arrayContainer: + return bc.ixorArray(x) + case *bitmapContainer: + return bc.ixorBitmap(x) + case *runContainer16: + return bc.ixorRun16(x) + } + panic("unsupported container type") +} + +func (bc *bitmapContainer) ixorArray(value2 *arrayContainer) container { + vbc := value2.toBitmapContainer() + return bc.ixorBitmap(vbc) +} + +func (bc *bitmapContainer) ixorRun16(value2 *runContainer16) container { + rcb := value2.toBitmapContainer() + return bc.ixorBitmap(rcb) +} + +func (bc *bitmapContainer) ixorBitmap(value2 *bitmapContainer) container { + newCardinality := int(popcntXorSlice(bc.bitmap, value2.bitmap)) + if newCardinality > arrayDefaultMaxSize { + for k := 0; k < len(bc.bitmap); k++ { + bc.bitmap[k] = bc.bitmap[k] ^ value2.bitmap[k] + } + bc.cardinality = newCardinality + return bc + } + ac := newArrayContainerSize(newCardinality) + fillArrayXOR(ac.content, bc.bitmap, value2.bitmap) + ac.content = ac.content[:newCardinality] + return ac +} + func (bc *bitmapContainer) andNot(a container) container { switch x := a.(type) { case *arrayContainer: diff --git a/roaring.go b/roaring.go index f511539a..afb38bce 100644 --- a/roaring.go +++ b/roaring.go @@ -1509,8 +1509,7 @@ func (rb *Bitmap) Xor(x2 *Bitmap) { pos1++ pos2++ } else { - // TODO: couple be computed in-place for reduced memory usage - c := rb.highlowcontainer.getContainerAtIndex(pos1).xor(x2.highlowcontainer.getContainerAtIndex(pos2)) + c := rb.highlowcontainer.getWritableContainerAtIndex(pos1).ixor(x2.highlowcontainer.getContainerAtIndex(pos2)) if !c.isEmpty() { rb.highlowcontainer.setContainerAtIndex(pos1, c) pos1++ diff --git a/roaringarray.go b/roaringarray.go index 31638d4b..49e1dd5c 100644 --- a/roaringarray.go +++ b/roaringarray.go @@ -39,6 +39,7 @@ type container interface { not(start, final int) container // range is [firstOfRange,lastOfRange) inot(firstOfRange, endx int) container // i stands for inplace, range is [firstOfRange,endx) xor(r container) container + ixor(r container) container // i stands for inplace getShortIterator() shortPeekable getUnsetIterator() shortPeekable iterate(cb func(x uint16) bool) bool diff --git a/runcontainer.go b/runcontainer.go index a435c4d2..851c66dc 100644 --- a/runcontainer.go +++ b/runcontainer.go @@ -2416,6 +2416,30 @@ func (rc *runContainer16) xor(a container) container { panic("unsupported container type") } +func (rc *runContainer16) ixor(a container) container { + switch c := a.(type) { + case *arrayContainer: + return rc.ixorArray(c) + case *bitmapContainer: + return rc.ixorBitmap(c) + case *runContainer16: + return rc.ixorRunContainer16(c) + } + panic("unsupported container type") +} + +func (rc *runContainer16) ixorArray(value2 *arrayContainer) container { + return rc.toBitmapContainer().ixor(value2) +} + +func (rc *runContainer16) ixorBitmap(value2 *bitmapContainer) container { + return value2.ixor(rc) +} + +func (rc *runContainer16) ixorRunContainer16(value2 *runContainer16) container { + return rc.toBitmapContainer().ixor(value2.toBitmapContainer()) +} + func (rc *runContainer16) iandNot(a container) container { switch c := a.(type) { case *arrayContainer: