diff --git a/lib/logstorage/bitmap.go b/lib/logstorage/bitmap.go index 2c0ccd8f4..c71efe8b9 100644 --- a/lib/logstorage/bitmap.go +++ b/lib/logstorage/bitmap.go @@ -146,6 +146,28 @@ func (bm *bitmap) forEachSetBit(f func(idx int) bool) { } } +// forEachSetBitReadonly calls f for each set bit +func (bm *bitmap) forEachSetBitReadonly(f func(idx int)) { + a := bm.a + bitsLen := bm.bitsLen + for i, word := range a { + if word == 0 { + continue + } + for j := 0; j < 64; j++ { + mask := uint64(1) << j + if (word & mask) == 0 { + continue + } + idx := i*64 + j + if idx >= bitsLen { + break + } + f(idx) + } + } +} + func (bm *bitmap) onesCount() int { n := 0 for _, word := range bm.a { diff --git a/lib/logstorage/bitmap_test.go b/lib/logstorage/bitmap_test.go index 4627e2055..1b5f2ba8b 100644 --- a/lib/logstorage/bitmap_test.go +++ b/lib/logstorage/bitmap_test.go @@ -32,7 +32,7 @@ func TestBitmap(t *testing.T) { // Make sure that all the bits are set. nextIdx := 0 - bm.forEachSetBit(func(idx int) bool { + bm.forEachSetBitReadonly(func(idx int) { if idx >= i { t.Fatalf("index must be smaller than %d", i) } @@ -40,7 +40,6 @@ func TestBitmap(t *testing.T) { t.Fatalf("unexpected idx; got %d; want %d", idx, nextIdx) } nextIdx++ - return true }) if !bm.areAllBitsSet() { @@ -66,12 +65,11 @@ func TestBitmap(t *testing.T) { } nextIdx = 1 - bm.forEachSetBit(func(idx int) bool { + bm.forEachSetBitReadonly(func(idx int) { if idx != nextIdx { t.Fatalf("unexpected idx; got %d; want %d", idx, nextIdx) } nextIdx += 2 - return true }) // Clear all the bits @@ -93,9 +91,8 @@ func TestBitmap(t *testing.T) { } bitsCount := 0 - bm.forEachSetBit(func(_ int) bool { + bm.forEachSetBitReadonly(func(_ int) { bitsCount++ - return true }) if bitsCount != 0 { t.Fatalf("unexpected non-zero number of set bits remained: %d", bitsCount) diff --git a/lib/logstorage/bitmap_timing_test.go b/lib/logstorage/bitmap_timing_test.go index 6bbcc8c8a..0430e1aaf 100644 --- a/lib/logstorage/bitmap_timing_test.go +++ b/lib/logstorage/bitmap_timing_test.go @@ -4,6 +4,35 @@ import ( "testing" ) +func BenchmarkBitmapForEachSetBitReadonly(b *testing.B) { + const bitsLen = 64*1024 + + b.Run("no-zero-bits", func(b *testing.B) { + bm := getBitmap(bitsLen) + bm.setBits() + benchmarkBitmapForEachSetBitReadonly(b, bm) + putBitmap(bm) + }) + b.Run("half-zero-bits", func(b *testing.B) { + bm := getBitmap(bitsLen) + bm.setBits() + bm.forEachSetBit(func(idx int) bool { + return idx%2 == 0 + }) + benchmarkBitmapForEachSetBitReadonly(b, bm) + putBitmap(bm) + }) + b.Run("one-set-bit", func(b *testing.B) { + bm := getBitmap(bitsLen) + bm.setBits() + bm.forEachSetBit(func(idx int) bool { + return idx == bitsLen/2 + }) + benchmarkBitmapForEachSetBitReadonly(b, bm) + putBitmap(bm) + }) +} + func BenchmarkBitmapForEachSetBit(b *testing.B) { const bitsLen = 64*1024 @@ -57,14 +86,33 @@ func BenchmarkBitmapForEachSetBit(b *testing.B) { }) } +func benchmarkBitmapForEachSetBitReadonly(b *testing.B, bm *bitmap) { + b.SetBytes(int64(bm.bitsLen)) + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + bmLocal := getBitmap(bm.bitsLen) + n := 0 + for pb.Next() { + bmLocal.copyFrom(bm) + bmLocal.forEachSetBitReadonly(func(idx int) { + n++ + }) + } + putBitmap(bmLocal) + GlobalSink.Add(uint64(n)) + }) +} + func benchmarkBitmapForEachSetBit(b *testing.B, bm *bitmap, isClearBits bool) { b.SetBytes(int64(bm.bitsLen)) b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { bmLocal := getBitmap(bm.bitsLen) + n := 0 for pb.Next() { bmLocal.copyFrom(bm) bmLocal.forEachSetBit(func(idx int) bool { + n++ return !isClearBits }) if isClearBits { @@ -78,5 +126,6 @@ func benchmarkBitmapForEachSetBit(b *testing.B, bm *bitmap, isClearBits bool) { } } putBitmap(bmLocal) + GlobalSink.Add(uint64(n)) }) } diff --git a/lib/logstorage/block_result.go b/lib/logstorage/block_result.go index 626d8365a..d14b2aba1 100644 --- a/lib/logstorage/block_result.go +++ b/lib/logstorage/block_result.go @@ -278,10 +278,9 @@ func (br *blockResult) mustInit(bs *blockSearch, bm *bitmap) { // Slow path - copy only the needed timestamps to br according to filter results. dstTimestamps := br.timestamps[:0] - bm.forEachSetBit(func(idx int) bool { + bm.forEachSetBitReadonly(func(idx int) { ts := srcTimestamps[idx] dstTimestamps = append(dstTimestamps, ts) - return true }) br.timestamps = dstTimestamps }