Skip to content

Commit dff6bc4

Browse files
committed
Add adaptive storage set.
Stores its contents differently depending on the number of items. - 0 items -> size=0 ptr=nil - 1 item -> size=1 ptr=<pointer to item> - 2-16 items -> size=n ptr=<pointer to array of items> - >16 -> size=0xff ptr=<pointer to map[T]struct{}>
1 parent 4ff3cb0 commit dff6bc4

File tree

2 files changed

+633
-0
lines changed

2 files changed

+633
-0
lines changed

libcalico-go/lib/set/adaptive.go

+282
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
package set
2+
3+
import (
4+
"unsafe"
5+
)
6+
7+
const (
8+
adaptiveSetArrayLimit = 16
9+
sizeInMap = 0xff
10+
)
11+
12+
// Adaptive is a set implementation that uses different underlying data
13+
// structures depending on the size of the set. For sets that usually empty
14+
// or have only one or two elements, it is more than twice as fast and it uses
15+
// ~10x less memory. It gets progressively slower as the number of elements
16+
// increases, but at adaptiveSetArrayLimit it switches to a map-based
17+
// implementation like set.Typed (with slight overhead relative to set.Typed).
18+
//
19+
// The zero value of Adaptive is an empty set; it should not be copied after
20+
// first use.
21+
type Adaptive[T comparable] struct {
22+
_ noCopy // Prevent copying of the set.
23+
24+
// p holds different types depending on the size of the set.
25+
// if size == 0, p is nil.
26+
// if size == 1, p is a pointer to the single element of the set.
27+
// if size is in the range [2, adaptiveSetArrayLimit], p is a pointer to an array of size elements.
28+
// if size > adaptiveSetArrayLimit, p is a pointer to a map[T]v
29+
p unsafe.Pointer
30+
31+
// size is either the number of elements in the set, or sizeInMap if the set is backed by a map.
32+
size uint8
33+
}
34+
35+
type noCopy struct{}
36+
37+
func (*noCopy) Lock() {}
38+
func (*noCopy) Unlock() {}
39+
40+
func NewAdaptive[T comparable]() *Adaptive[T] {
41+
return &Adaptive[T]{}
42+
}
43+
44+
func (a *Adaptive[T]) Len() int {
45+
if a.size == sizeInMap {
46+
return len(*(*map[T]v)(a.p))
47+
}
48+
return int(a.size)
49+
}
50+
51+
func (a *Adaptive[T]) Add(item T) {
52+
switch a.size {
53+
case 0:
54+
a.p = unsafe.Pointer(&item)
55+
a.size = 1
56+
case 1:
57+
theOne := (*T)(a.p)
58+
if *theOne == item {
59+
// The element is already in the set.
60+
return
61+
}
62+
// Element is different from the one already in the set.
63+
// Need to upgrade to an array.
64+
arr := [2]T{*theOne, item}
65+
a.p = unsafe.Pointer(&arr[0])
66+
a.size = 2
67+
case sizeInMap:
68+
m := *(*map[T]v)(a.p)
69+
m[item] = emptyValue
70+
default:
71+
tPtr := (*T)(a.p)
72+
tSlice := unsafe.Slice(tPtr, a.size)
73+
for _, t := range tSlice {
74+
if t == item {
75+
// The element is already in the set.
76+
return
77+
}
78+
}
79+
if a.size < adaptiveSetArrayLimit {
80+
// Still allowed to grow the slice.
81+
s2 := make([]T, a.size+1)
82+
copy(s2, tSlice)
83+
s2[a.size] = item
84+
a.p = unsafe.Pointer(&s2[0])
85+
a.size++
86+
return
87+
}
88+
// Need to upgrade to a map.
89+
m := make(map[T]v, a.size+1)
90+
for _, t := range tSlice {
91+
m[t] = emptyValue
92+
}
93+
m[item] = emptyValue
94+
a.p = unsafe.Pointer(&m)
95+
a.size = sizeInMap
96+
}
97+
}
98+
99+
func (a *Adaptive[T]) AddAll(itemArray []T) {
100+
for _, v := range itemArray {
101+
a.Add(v)
102+
}
103+
}
104+
105+
func (a *Adaptive[T]) AddSet(other Set[T]) {
106+
other.Iter(func(item T) error {
107+
a.Add(item)
108+
return nil
109+
})
110+
}
111+
112+
func (a *Adaptive[T]) Discard(item T) {
113+
switch a.size {
114+
case 0:
115+
return
116+
case 1:
117+
theOne := (*T)(a.p)
118+
if *theOne == item {
119+
a.p = nil
120+
a.size = 0
121+
}
122+
case 2:
123+
tSlice := (*[2]T)(a.p)[:]
124+
if tSlice[0] == item {
125+
a.p = unsafe.Pointer(&tSlice[1])
126+
a.size = 1
127+
return
128+
}
129+
if tSlice[1] == item {
130+
a.p = unsafe.Pointer(&tSlice[0])
131+
a.size = 1
132+
return
133+
}
134+
case sizeInMap:
135+
m := *(*map[T]v)(a.p)
136+
delete(m, item)
137+
if len(m) <= adaptiveSetArrayLimit {
138+
// Downgrade to an array.
139+
s := make([]T, 0, len(m))
140+
for t := range m {
141+
s = append(s, t)
142+
}
143+
a.p = unsafe.Pointer(&s[0])
144+
a.size = uint8(len(m))
145+
}
146+
default:
147+
tPtr := (*T)(a.p)
148+
tSlice := unsafe.Slice(tPtr, a.size)
149+
updated := make([]T, 0, a.size-1)
150+
for _, t := range tSlice {
151+
if t == item {
152+
continue
153+
}
154+
if len(updated) == int(a.size-1) {
155+
return
156+
}
157+
updated = append(updated, t)
158+
}
159+
a.size--
160+
a.p = unsafe.Pointer(&updated[0])
161+
}
162+
}
163+
164+
func (a *Adaptive[T]) Clear() {
165+
a.size = 0
166+
a.p = nil
167+
}
168+
169+
func (a *Adaptive[T]) Contains(t T) bool {
170+
if a.size == 0 {
171+
return false
172+
}
173+
if a.size == 1 {
174+
return *(*T)(a.p) == t
175+
}
176+
if a.size <= adaptiveSetArrayLimit {
177+
tSlice := unsafe.Slice((*T)(a.p), a.size)
178+
for _, v := range tSlice {
179+
if v == t {
180+
return true
181+
}
182+
}
183+
return false
184+
}
185+
m := *(*map[T]v)(a.p)
186+
_, present := m[t]
187+
return present
188+
}
189+
190+
func (a *Adaptive[T]) Iter(f func(item T) error) {
191+
if a.size == 0 {
192+
return
193+
}
194+
if a.size == 1 {
195+
err := f(*(*T)(a.p))
196+
if err == StopIteration {
197+
return
198+
}
199+
if err == RemoveItem {
200+
a.size = 0
201+
a.p = nil
202+
return
203+
}
204+
return
205+
}
206+
if a.size <= adaptiveSetArrayLimit {
207+
tSlice := unsafe.Slice((*T)(a.p), a.size)
208+
for _, v := range tSlice {
209+
err := f(v)
210+
if err == StopIteration {
211+
return
212+
}
213+
if err == RemoveItem {
214+
a.Discard(v)
215+
}
216+
}
217+
return
218+
}
219+
m := *(*map[T]v)(a.p)
220+
for v := range m {
221+
err := f(v)
222+
if err == StopIteration {
223+
return
224+
}
225+
if err == RemoveItem {
226+
a.Discard(v)
227+
}
228+
}
229+
}
230+
231+
func (a *Adaptive[T]) Copy() Set[T] {
232+
other := NewAdaptive[T]()
233+
a.Iter(func(item T) error {
234+
other.Add(item)
235+
return nil
236+
})
237+
return other
238+
}
239+
240+
func (a *Adaptive[T]) Equals(s Set[T]) bool {
241+
if a.Len() != s.Len() {
242+
return false
243+
}
244+
equal := true
245+
a.Iter(func(item T) error {
246+
if !s.Contains(item) {
247+
equal = false
248+
return StopIteration
249+
}
250+
return nil
251+
})
252+
return equal
253+
}
254+
255+
func (a *Adaptive[T]) ContainsAll(s Set[T]) bool {
256+
seenAll := true
257+
s.Iter(func(item T) error {
258+
if !a.Contains(item) {
259+
seenAll = false
260+
return StopIteration
261+
}
262+
return nil
263+
})
264+
return seenAll
265+
}
266+
267+
func (a *Adaptive[T]) Slice() []T {
268+
s := make([]T, 0, a.size)
269+
a.Iter(func(item T) error {
270+
s = append(s, item)
271+
return nil
272+
})
273+
return s
274+
}
275+
276+
func (a *Adaptive[T]) String() string {
277+
s := New[T]()
278+
s.AddSet(a)
279+
return s.String()
280+
}
281+
282+
var _ Set[any] = &Adaptive[any]{}

0 commit comments

Comments
 (0)