diff --git a/core/filtermaps/bitset.go b/core/filtermaps/bitset.go
new file mode 100644
index 000000000000..c8fc9cdee838
--- /dev/null
+++ b/core/filtermaps/bitset.go
@@ -0,0 +1,135 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package filtermaps
+
+import "math/bits"
+
+// indexBitset represents a set of indices using a bitmap.
+type indexBitset struct {
+ minIndex uint32
+ maxIndex uint32
+ bits []uint64
+}
+
+// newIndexBitset creates a bitset from a list of indices.
+// Returns an empty bitset if indices is empty.
+func newIndexBitset(indices []uint32) *indexBitset {
+ if len(indices) == 0 {
+ return &indexBitset{}
+ }
+ // Find index range
+ minIdx, maxIdx := indices[0], indices[0]
+ for _, idx := range indices[1:] {
+ if idx < minIdx {
+ minIdx = idx
+ }
+ if idx > maxIdx {
+ maxIdx = idx
+ }
+ }
+ // Calculate number of uint64 needed
+ rangeSize := maxIdx - minIdx + 1
+ bitsCount := (rangeSize + 63) / 64
+ bitset := &indexBitset{
+ minIndex: minIdx,
+ maxIndex: maxIdx,
+ bits: make([]uint64, bitsCount),
+ }
+ // Set all specified indices
+ for _, idx := range indices {
+ bitset.Set(idx)
+ }
+ return bitset
+}
+
+// Has checks if an index exists in the set.
+func (b *indexBitset) Has(idx uint32) bool {
+ if b.bits == nil || idx < b.minIndex || idx > b.maxIndex {
+ return false
+ }
+ pos := idx - b.minIndex
+ wordIdx := pos / 64
+ bitIdx := pos % 64
+ return (b.bits[wordIdx] & (1 << bitIdx)) != 0
+}
+
+// Set adds an index to the set.
+func (b *indexBitset) Set(idx uint32) {
+ if b.bits == nil || idx < b.minIndex || idx > b.maxIndex {
+ return
+ }
+ pos := idx - b.minIndex
+ wordIdx := pos / 64
+ bitIdx := pos % 64
+ b.bits[wordIdx] |= 1 << bitIdx
+}
+
+// Clear removes an index from the set.
+func (b *indexBitset) Clear(idx uint32) {
+ if b.bits == nil || idx < b.minIndex || idx > b.maxIndex {
+ return
+ }
+ pos := idx - b.minIndex
+ wordIdx := pos / 64
+ bitIdx := pos % 64
+ b.bits[wordIdx] &^= 1 << bitIdx
+}
+
+// Count returns the number of indices in the set.
+func (b *indexBitset) Count() int {
+ if b.bits == nil {
+ return 0
+ }
+ count := 0
+ for _, word := range b.bits {
+ count += bits.OnesCount64(word)
+ }
+ return count
+}
+
+// IsEmpty checks if the set is empty.
+func (b *indexBitset) IsEmpty() bool {
+ if b.bits == nil {
+ return true
+ }
+ for _, word := range b.bits {
+ if word != 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// Iterate traverses all indices in the set.
+// The callback function fn is called with each index in the set.
+// Iteration order is from smallest to largest.
+func (b *indexBitset) Iterate(fn func(uint32)) {
+ if b.bits == nil {
+ return
+ }
+ for i, word := range b.bits {
+ if word == 0 {
+ continue
+ }
+ baseIdx := b.minIndex + uint32(i*64)
+ for bitIdx := 0; bitIdx < 64; bitIdx++ {
+ if (word & (1 << bitIdx)) != 0 {
+ fn(baseIdx + uint32(bitIdx))
+ }
+ }
+ }
+}
diff --git a/core/filtermaps/bitset_test.go b/core/filtermaps/bitset_test.go
new file mode 100644
index 000000000000..ad2261832256
--- /dev/null
+++ b/core/filtermaps/bitset_test.go
@@ -0,0 +1,170 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package filtermaps
+
+import (
+ "testing"
+)
+
+func TestBitsetBasic(t *testing.T) {
+ indices := []uint32{100, 101, 105, 110, 115}
+ bs := newIndexBitset(indices)
+ // Test Has - existing indices
+ for _, idx := range indices {
+ if !bs.Has(idx) {
+ t.Errorf("Expected Has(%d) = true", idx)
+ }
+ }
+ // Test Has - non-existing indices
+ notInSet := []uint32{99, 102, 103, 104, 106, 107, 108, 109, 111, 116}
+ for _, idx := range notInSet {
+ if bs.Has(idx) {
+ t.Errorf("Expected Has(%d) = false", idx)
+ }
+ }
+ // Test Count
+ if count := bs.Count(); count != len(indices) {
+ t.Errorf("Expected Count = %d, got %d", len(indices), count)
+ }
+}
+
+func TestBitsetClear(t *testing.T) {
+ indices := []uint32{100, 101, 105, 110, 115}
+ bs := newIndexBitset(indices)
+ // Clear an index
+ bs.Clear(105)
+ if bs.Has(105) {
+ t.Error("Expected Has(105) = false after Clear")
+ }
+ // Count should decrease
+ if count := bs.Count(); count != 4 {
+ t.Errorf("Expected Count = 4, got %d", count)
+ }
+ // Other indices should remain unaffected
+ if !bs.Has(100) || !bs.Has(101) || !bs.Has(110) || !bs.Has(115) {
+ t.Error("Other indices should remain")
+ }
+}
+
+func TestBitsetSet(t *testing.T) {
+ indices := []uint32{100, 105, 110}
+ bs := newIndexBitset(indices)
+ // Set a new index
+ bs.Set(102)
+ if !bs.Has(102) {
+ t.Error("Expected Has(102) = true after Set")
+ }
+ // Count should increase
+ if count := bs.Count(); count != 4 {
+ t.Errorf("Expected Count = 4, got %d", count)
+ }
+}
+
+func TestBitsetIterate(t *testing.T) {
+ indices := []uint32{100, 101, 105, 110, 115}
+ bs := newIndexBitset(indices)
+ // Collect iterated indices
+ var collected []uint32
+ bs.Iterate(func(idx uint32) {
+ collected = append(collected, idx)
+ })
+ // Verify count
+ if len(collected) != len(indices) {
+ t.Errorf("Expected %d indices, got %d", len(indices), len(collected))
+ }
+ // Verify all indices are present
+ for _, idx := range indices {
+ found := false
+ for _, c := range collected {
+ if c == idx {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("Index %d not found in iteration", idx)
+ }
+ }
+}
+
+// Benchmark: Bitset vs Map
+func BenchmarkBitsetVsMap(b *testing.B) {
+ // Generate test data: 1000 consecutive indices
+ indices := make([]uint32, 1000)
+ for i := range indices {
+ indices[i] = uint32(5000 + i)
+ }
+
+ b.Run("Bitset_Has", func(b *testing.B) {
+ bs := newIndexBitset(indices)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = bs.Has(5500)
+ }
+ })
+
+ b.Run("Map_Has", func(b *testing.B) {
+ m := make(map[uint32]struct{})
+ for _, idx := range indices {
+ m[idx] = struct{}{}
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, _ = m[5500]
+ }
+ })
+
+ b.Run("Bitset_Set", func(b *testing.B) {
+ bs := newIndexBitset(indices)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ bs.Set(5500)
+ }
+ })
+
+ b.Run("Map_Set", func(b *testing.B) {
+ m := make(map[uint32]struct{})
+ for _, idx := range indices {
+ m[idx] = struct{}{}
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ m[5500] = struct{}{}
+ }
+ })
+
+ b.Run("Bitset_Clear", func(b *testing.B) {
+ bs := newIndexBitset(indices)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ bs.Clear(5500)
+ bs.Set(5500) // Reset for next iteration
+ }
+ })
+
+ b.Run("Map_Delete", func(b *testing.B) {
+ m := make(map[uint32]struct{})
+ for _, idx := range indices {
+ m[idx] = struct{}{}
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ delete(m, 5500)
+ m[5500] = struct{}{} // Re-add for next iteration
+ }
+ })
+}
diff --git a/core/filtermaps/map_renderer.go b/core/filtermaps/map_renderer.go
index e1284a382977..9066b7d88145 100644
--- a/core/filtermaps/map_renderer.go
+++ b/core/filtermaps/map_renderer.go
@@ -48,14 +48,17 @@ var (
// mapRenderer represents a process that renders filter maps in a specified
// range according to the actual targetView.
type mapRenderer struct {
- f *FilterMaps
- renderBefore uint32
- currentMap *renderedMap
- finishedMaps map[uint32]*renderedMap
- finished common.Range[uint32]
- iterator *logIterator
+ f *FilterMaps
+ renderBefore uint32
+ currentMap *renderedMap
+ finishedMaps map[uint32]*renderedMap
+ finished common.Range[uint32]
+ iterator *logIterator
+ rowMappingCache *lru.Cache[common.Hash, lvPos]
}
+type lvPos struct{ rowIndex, layerIndex uint32 }
+
// renderedMap represents a single filter map that is being rendered in memory.
type renderedMap struct {
filterMap filterMap
@@ -110,10 +113,11 @@ func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, erro
lastBlock: cp.lastBlock,
blockLvPtrs: slices.Clone(cp.blockLvPtrs),
},
- finishedMaps: make(map[uint32]*renderedMap),
- finished: common.NewRange(cp.mapIndex, 0),
- renderBefore: math.MaxUint32,
- iterator: iter,
+ finishedMaps: make(map[uint32]*renderedMap),
+ finished: common.NewRange(cp.mapIndex, 0),
+ renderBefore: math.MaxUint32,
+ iterator: iter,
+ rowMappingCache: lru.NewCache[common.Hash, lvPos](cachedRowMappings),
}, nil
}
@@ -131,10 +135,11 @@ func (f *FilterMaps) renderMapsFromMapBoundary(firstMap, renderBefore uint32, st
mapIndex: firstMap,
lastBlock: iter.blockNumber,
},
- finishedMaps: make(map[uint32]*renderedMap),
- finished: common.NewRange(firstMap, 0),
- renderBefore: renderBefore,
- iterator: iter,
+ finishedMaps: make(map[uint32]*renderedMap),
+ finished: common.NewRange(firstMap, 0),
+ renderBefore: renderBefore,
+ iterator: iter,
+ rowMappingCache: lru.NewCache[common.Hash, lvPos](cachedRowMappings),
}, nil
}
@@ -319,9 +324,8 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
if r.iterator.lvIndex == 0 {
r.currentMap.blockLvPtrs = []uint64{0}
}
- type lvPos struct{ rowIndex, layerIndex uint32 }
- rowMappingCache := lru.NewCache[common.Hash, lvPos](cachedRowMappings)
- defer rowMappingCache.Purge()
+ // Clear the cache for this map rendering
+ r.rowMappingCache.Purge()
for r.iterator.lvIndex < uint64(r.currentMap.mapIndex+1)< 0 {
return false
}
}
- delete(m.baseRequested, mapIndex)
+ m.baseRequested.Clear(mapIndex)
return true
}
@@ -827,15 +822,15 @@ func (m *matchSequenceInstance) dropBase(mapIndex uint32) bool {
// matcher based on the known results from the base matcher and removes it
// from the internal requested set and returns true if possible.
func (m *matchSequenceInstance) dropNext(mapIndex uint32) bool {
- if _, ok := m.nextRequested[mapIndex]; !ok {
+ if !m.nextRequested.Has(mapIndex) {
return false
}
- if _, ok := m.needMatched[mapIndex]; ok {
+ if m.needMatched.Has(mapIndex) {
if base := m.baseResults[mapIndex]; base == nil || len(base) > 0 {
return false
}
}
- delete(m.nextRequested, mapIndex)
+ m.nextRequested.Clear(mapIndex)
return true
}