Skip to content

Commit fb79b44

Browse files
Add bitmap-based tool index for O(1) filtering
This implements a high-performance bitmap index for tool filtering: - ToolBitmap: 256-bit bitmap (4 uint64s) with O(1) set operations - ToolIndex: Pre-computed index for toolset, read-only, and feature flag filtering - Query: Returns guaranteed tools + those needing dynamic checks - Materialize: Only runs dynamic Enabled() checks on survivors Performance benchmarks: - Query (2 toolsets): 78 ns/op, 0 allocs - Query (all toolsets): 44 ns/op, 0 allocs - Bitmap OR/AND: <0.3 ns/op - Full query + materialize (130 tools): 5.4 µs/op, 4 allocs Key insight: Static filters (toolset, read-only, feature flags) can be pre-computed as bitmaps. Dynamic Enabled() checks are only run on tools that survive static filtering, avoiding wasteful checks on tools that would be filtered out anyway.
1 parent 97feb5c commit fb79b44

File tree

4 files changed

+1230
-0
lines changed

4 files changed

+1230
-0
lines changed

pkg/inventory/bitmap.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package inventory
2+
3+
import (
4+
"math/bits"
5+
)
6+
7+
// ToolBitmap represents a set of tools as a bitmap for O(1) set operations.
8+
// Uses 4 uint64s (256 bits) to support up to 256 tools.
9+
// Current tool count is ~130, so this provides headroom for growth.
10+
type ToolBitmap [4]uint64
11+
12+
// EmptyBitmap returns an empty bitmap with no bits set.
13+
func EmptyBitmap() ToolBitmap {
14+
return ToolBitmap{}
15+
}
16+
17+
// SetBit returns a new bitmap with the bit at position i set.
18+
func (b ToolBitmap) SetBit(i int) ToolBitmap {
19+
if i < 0 || i >= 256 {
20+
return b
21+
}
22+
word, bit := i/64, uint(i%64) //nolint:gosec // bounds checked above
23+
result := b
24+
result[word] |= 1 << bit
25+
return result
26+
}
27+
28+
// ClearBit returns a new bitmap with the bit at position i cleared.
29+
func (b ToolBitmap) ClearBit(i int) ToolBitmap {
30+
if i < 0 || i >= 256 {
31+
return b
32+
}
33+
word, bit := i/64, uint(i%64) //nolint:gosec // bounds checked above
34+
result := b
35+
result[word] &^= 1 << bit
36+
return result
37+
}
38+
39+
// IsSet returns true if the bit at position i is set.
40+
func (b ToolBitmap) IsSet(i int) bool {
41+
if i < 0 || i >= 256 {
42+
return false
43+
}
44+
word, bit := i/64, uint(i%64) //nolint:gosec // bounds checked above
45+
return (b[word] & (1 << bit)) != 0
46+
}
47+
48+
// Or returns the union of two bitmaps.
49+
func (b ToolBitmap) Or(other ToolBitmap) ToolBitmap {
50+
return ToolBitmap{
51+
b[0] | other[0],
52+
b[1] | other[1],
53+
b[2] | other[2],
54+
b[3] | other[3],
55+
}
56+
}
57+
58+
// And returns the intersection of two bitmaps.
59+
func (b ToolBitmap) And(other ToolBitmap) ToolBitmap {
60+
return ToolBitmap{
61+
b[0] & other[0],
62+
b[1] & other[1],
63+
b[2] & other[2],
64+
b[3] & other[3],
65+
}
66+
}
67+
68+
// AndNot returns b AND NOT other (bits in b that are not in other).
69+
func (b ToolBitmap) AndNot(other ToolBitmap) ToolBitmap {
70+
return ToolBitmap{
71+
b[0] &^ other[0],
72+
b[1] &^ other[1],
73+
b[2] &^ other[2],
74+
b[3] &^ other[3],
75+
}
76+
}
77+
78+
// PopCount returns the number of set bits (population count).
79+
func (b ToolBitmap) PopCount() int {
80+
return bits.OnesCount64(b[0]) +
81+
bits.OnesCount64(b[1]) +
82+
bits.OnesCount64(b[2]) +
83+
bits.OnesCount64(b[3])
84+
}
85+
86+
// IsEmpty returns true if no bits are set.
87+
func (b ToolBitmap) IsEmpty() bool {
88+
return b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0
89+
}
90+
91+
// Iterate calls fn for each set bit position. Stops if fn returns false.
92+
func (b ToolBitmap) Iterate(fn func(position int) bool) {
93+
for word := 0; word < 4; word++ {
94+
v := b[word]
95+
base := word * 64
96+
for v != 0 {
97+
// Find position of lowest set bit
98+
tz := bits.TrailingZeros64(v)
99+
if !fn(base + tz) {
100+
return
101+
}
102+
// Clear the lowest set bit
103+
v &= v - 1
104+
}
105+
}
106+
}
107+
108+
// Positions returns a slice of all set bit positions.
109+
func (b ToolBitmap) Positions() []int {
110+
result := make([]int, 0, b.PopCount())
111+
b.Iterate(func(pos int) bool {
112+
result = append(result, pos)
113+
return true
114+
})
115+
return result
116+
}

pkg/inventory/bitmap_test.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package inventory
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestToolBitmap_SetAndIsSet(t *testing.T) {
10+
t.Parallel()
11+
12+
var bm ToolBitmap
13+
14+
// Initially empty
15+
assert.False(t, bm.IsSet(0))
16+
assert.False(t, bm.IsSet(63))
17+
assert.False(t, bm.IsSet(64))
18+
assert.False(t, bm.IsSet(127))
19+
20+
// Set some bits
21+
bm = bm.SetBit(0)
22+
bm = bm.SetBit(63)
23+
bm = bm.SetBit(64)
24+
bm = bm.SetBit(127)
25+
bm = bm.SetBit(200)
26+
27+
assert.True(t, bm.IsSet(0))
28+
assert.True(t, bm.IsSet(63))
29+
assert.True(t, bm.IsSet(64))
30+
assert.True(t, bm.IsSet(127))
31+
assert.True(t, bm.IsSet(200))
32+
33+
// Unset bits should still be false
34+
assert.False(t, bm.IsSet(1))
35+
assert.False(t, bm.IsSet(62))
36+
assert.False(t, bm.IsSet(128))
37+
}
38+
39+
func TestToolBitmap_ClearBit(t *testing.T) {
40+
t.Parallel()
41+
42+
bm := ToolBitmap{}.SetBit(5).SetBit(10).SetBit(100)
43+
assert.True(t, bm.IsSet(5))
44+
assert.True(t, bm.IsSet(10))
45+
assert.True(t, bm.IsSet(100))
46+
47+
bm = bm.ClearBit(10)
48+
assert.True(t, bm.IsSet(5))
49+
assert.False(t, bm.IsSet(10))
50+
assert.True(t, bm.IsSet(100))
51+
}
52+
53+
func TestToolBitmap_Or(t *testing.T) {
54+
t.Parallel()
55+
56+
a := ToolBitmap{}.SetBit(1).SetBit(3).SetBit(65)
57+
b := ToolBitmap{}.SetBit(2).SetBit(3).SetBit(130)
58+
59+
result := a.Or(b)
60+
61+
assert.True(t, result.IsSet(1))
62+
assert.True(t, result.IsSet(2))
63+
assert.True(t, result.IsSet(3))
64+
assert.True(t, result.IsSet(65))
65+
assert.True(t, result.IsSet(130))
66+
assert.False(t, result.IsSet(0))
67+
assert.False(t, result.IsSet(4))
68+
}
69+
70+
func TestToolBitmap_And(t *testing.T) {
71+
t.Parallel()
72+
73+
a := ToolBitmap{}.SetBit(1).SetBit(3).SetBit(5).SetBit(65)
74+
b := ToolBitmap{}.SetBit(3).SetBit(5).SetBit(7).SetBit(65)
75+
76+
result := a.And(b)
77+
78+
assert.False(t, result.IsSet(1)) // only in a
79+
assert.True(t, result.IsSet(3)) // in both
80+
assert.True(t, result.IsSet(5)) // in both
81+
assert.False(t, result.IsSet(7)) // only in b
82+
assert.True(t, result.IsSet(65)) // in both
83+
}
84+
85+
func TestToolBitmap_AndNot(t *testing.T) {
86+
t.Parallel()
87+
88+
a := ToolBitmap{}.SetBit(1).SetBit(3).SetBit(5).SetBit(65)
89+
b := ToolBitmap{}.SetBit(3).SetBit(7).SetBit(65)
90+
91+
result := a.AndNot(b)
92+
93+
assert.True(t, result.IsSet(1)) // in a, not in b
94+
assert.False(t, result.IsSet(3)) // in both, removed
95+
assert.True(t, result.IsSet(5)) // in a, not in b
96+
assert.False(t, result.IsSet(7)) // not in a
97+
assert.False(t, result.IsSet(65)) // in both, removed
98+
}
99+
100+
func TestToolBitmap_PopCount(t *testing.T) {
101+
t.Parallel()
102+
103+
assert.Equal(t, 0, ToolBitmap{}.PopCount())
104+
assert.Equal(t, 1, ToolBitmap{}.SetBit(0).PopCount())
105+
assert.Equal(t, 2, ToolBitmap{}.SetBit(0).SetBit(100).PopCount())
106+
assert.Equal(t, 4, ToolBitmap{}.SetBit(0).SetBit(63).SetBit(64).SetBit(255).PopCount())
107+
}
108+
109+
func TestToolBitmap_IsEmpty(t *testing.T) {
110+
t.Parallel()
111+
112+
assert.True(t, ToolBitmap{}.IsEmpty())
113+
assert.False(t, ToolBitmap{}.SetBit(0).IsEmpty())
114+
assert.False(t, ToolBitmap{}.SetBit(200).IsEmpty())
115+
}
116+
117+
func TestToolBitmap_Iterate(t *testing.T) {
118+
t.Parallel()
119+
120+
bm := ToolBitmap{}.SetBit(3).SetBit(10).SetBit(64).SetBit(100).SetBit(200)
121+
122+
var positions []int
123+
bm.Iterate(func(pos int) bool {
124+
positions = append(positions, pos)
125+
return true
126+
})
127+
128+
assert.Equal(t, []int{3, 10, 64, 100, 200}, positions)
129+
}
130+
131+
func TestToolBitmap_Iterate_EarlyStop(t *testing.T) {
132+
t.Parallel()
133+
134+
bm := ToolBitmap{}.SetBit(1).SetBit(5).SetBit(10).SetBit(20)
135+
136+
var positions []int
137+
bm.Iterate(func(pos int) bool {
138+
positions = append(positions, pos)
139+
return pos < 10 // stop after 10
140+
})
141+
142+
assert.Equal(t, []int{1, 5, 10}, positions)
143+
}
144+
145+
func TestToolBitmap_Positions(t *testing.T) {
146+
t.Parallel()
147+
148+
bm := ToolBitmap{}.SetBit(5).SetBit(63).SetBit(64).SetBit(128)
149+
positions := bm.Positions()
150+
151+
assert.Equal(t, []int{5, 63, 64, 128}, positions)
152+
}
153+
154+
func TestToolBitmap_BoundaryConditions(t *testing.T) {
155+
t.Parallel()
156+
157+
var bm ToolBitmap
158+
159+
// Negative index should be no-op
160+
bm = bm.SetBit(-1)
161+
assert.False(t, bm.IsSet(-1))
162+
163+
// Index >= 256 should be no-op
164+
bm = bm.SetBit(256)
165+
assert.False(t, bm.IsSet(256))
166+
167+
// Maximum valid index
168+
bm = bm.SetBit(255)
169+
assert.True(t, bm.IsSet(255))
170+
}
171+
172+
func BenchmarkToolBitmap_Or(b *testing.B) {
173+
a := ToolBitmap{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0, 0}
174+
c := ToolBitmap{0, 0, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}
175+
176+
b.ResetTimer()
177+
for i := 0; i < b.N; i++ {
178+
_ = a.Or(c)
179+
}
180+
}
181+
182+
func BenchmarkToolBitmap_And(b *testing.B) {
183+
a := ToolBitmap{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xAAAAAAAAAAAAAAAA, 0}
184+
c := ToolBitmap{0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0}
185+
186+
b.ResetTimer()
187+
for i := 0; i < b.N; i++ {
188+
_ = a.And(c)
189+
}
190+
}
191+
192+
func BenchmarkToolBitmap_PopCount(b *testing.B) {
193+
bm := ToolBitmap{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0xFFFFFFFF00000000, 0x00000000FFFFFFFF}
194+
195+
b.ResetTimer()
196+
for i := 0; i < b.N; i++ {
197+
_ = bm.PopCount()
198+
}
199+
}
200+
201+
func BenchmarkToolBitmap_Iterate130Bits(b *testing.B) {
202+
// Simulate ~130 tools
203+
var bm ToolBitmap
204+
for i := 0; i < 130; i++ {
205+
bm = bm.SetBit(i)
206+
}
207+
208+
b.ResetTimer()
209+
for i := 0; i < b.N; i++ {
210+
count := 0
211+
bm.Iterate(func(_ int) bool {
212+
count++
213+
return true
214+
})
215+
}
216+
}

0 commit comments

Comments
 (0)