Skip to content

Commit 62e6fa6

Browse files
authored
planner: add FastIntSet for functional dependency maintaince (#33021)
ref #29766
1 parent 644b783 commit 62e6fa6

File tree

4 files changed

+1091
-0
lines changed

4 files changed

+1091
-0
lines changed

planner/funcdep/fast_int_set.go

+390
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
// Copyright 2022 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package funcdep
16+
17+
import (
18+
"bytes"
19+
"fmt"
20+
"math/bits"
21+
22+
"golang.org/x/tools/container/intsets"
23+
)
24+
25+
const smallCutOff = 64
26+
27+
// FastIntSet is wrapper of sparse with an optimization that number [0 ~ 64) can be cached for quick access.
28+
// From the benchmark in fd_graph_test.go, we choose to use sparse to accelerate int set. And when the set
29+
// size is quite small we can just skip the block allocation in the sparse chain list.
30+
type FastIntSet struct {
31+
// an uint64 used like quick-small bitmap of 0~63.
32+
small uint64
33+
// when some is bigger than 64, then all that previous inserted will be dumped into sparse.
34+
large *intsets.Sparse
35+
}
36+
37+
// NewFastIntSet is used to make the instance of FastIntSet with initial values.
38+
func NewFastIntSet(values ...int) FastIntSet {
39+
var res FastIntSet
40+
for _, v := range values {
41+
res.Insert(v)
42+
}
43+
return res
44+
}
45+
46+
// Len return the size of the int set.
47+
func (s FastIntSet) Len() int {
48+
if s.large == nil {
49+
return bits.OnesCount64(s.small)
50+
}
51+
return s.large.Len()
52+
}
53+
54+
// Only1Zero is a usage function for convenience judgement.
55+
func (s FastIntSet) Only1Zero() bool {
56+
return s.Len() == 1 && s.Has(0)
57+
}
58+
59+
// Insert is used to insert a value into int-set.
60+
func (s *FastIntSet) Insert(i int) {
61+
isSmall := i >= 0 && i < smallCutOff
62+
if isSmall {
63+
s.small |= 1 << uint64(i)
64+
}
65+
if !isSmall && s.large == nil {
66+
// first encounter a larger/smaller number, dump all that in `small` into `large`.
67+
s.large = s.toLarge()
68+
}
69+
if s.large != nil {
70+
s.large.Insert(i)
71+
}
72+
}
73+
74+
func (s FastIntSet) toLarge() *intsets.Sparse {
75+
if s.large != nil {
76+
return s.large
77+
}
78+
large := new(intsets.Sparse)
79+
for i, ok := s.Next(0); ok; i, ok = s.Next(i + 1) {
80+
large.Insert(i)
81+
}
82+
return large
83+
}
84+
85+
// Next returns the next existing number in the Set. If there's no larger one than the given start val, return (MaxInt, false).
86+
func (s FastIntSet) Next(startVal int) (int, bool) {
87+
if startVal < smallCutOff {
88+
if startVal < 0 {
89+
startVal = 0
90+
}
91+
// x=0, gap=64, which means there is no `1` after right shift.
92+
if gap := bits.TrailingZeros64(s.small >> uint64(startVal)); gap < 64 {
93+
return gap + startVal, true
94+
}
95+
}
96+
if s.large != nil {
97+
res := s.large.LowerBound(startVal)
98+
return res, res != intsets.MaxInt
99+
}
100+
return intsets.MaxInt, false
101+
}
102+
103+
// Remove is used to remove a value from the set. Nothing done if the value is not in the set.
104+
func (s *FastIntSet) Remove(i int) {
105+
if i >= 0 && i < smallCutOff {
106+
s.small &^= 1 << uint64(i)
107+
}
108+
if s.large != nil {
109+
s.large.Remove(i)
110+
}
111+
}
112+
113+
// Clear is used to clear a fastIntSet and reuse it as an empty one.
114+
func (s *FastIntSet) Clear() {
115+
s.small = 0
116+
if s.large != nil {
117+
s.large.Clear()
118+
}
119+
}
120+
121+
// Has is used ot judge whether a value is in the set.
122+
func (s FastIntSet) Has(i int) bool {
123+
if i >= 0 && i < smallCutOff {
124+
return (s.small & (1 << uint64(i))) != 0
125+
}
126+
if s.large != nil {
127+
return s.large.Has(i)
128+
}
129+
return false
130+
}
131+
132+
// IsEmpty is used to judge whether the int-set is empty.
133+
func (s FastIntSet) IsEmpty() bool {
134+
return s.small == 0 && (s.large == nil || s.large.IsEmpty())
135+
}
136+
137+
// SortedArray is used to return the in array of the set.
138+
func (s FastIntSet) SortedArray() []int {
139+
if s.IsEmpty() {
140+
return nil
141+
}
142+
if s.large != nil {
143+
return s.large.AppendTo([]int(nil))
144+
}
145+
res := make([]int, 0, s.Len())
146+
s.ForEach(func(i int) {
147+
res = append(res, i)
148+
})
149+
return res
150+
}
151+
152+
// ForEach call a function for each value in the int-set. (Ascend)
153+
func (s FastIntSet) ForEach(f func(i int)) {
154+
if s.large != nil {
155+
for x := s.large.Min(); x != intsets.MaxInt; x = s.large.LowerBound(x + 1) {
156+
f(x)
157+
}
158+
return
159+
}
160+
for v := s.small; v != 0; {
161+
// from the left to right.
162+
i := bits.TrailingZeros64(v)
163+
f(i)
164+
v &^= 1 << uint(i)
165+
}
166+
}
167+
168+
// Copy returns a copy of s.
169+
func (s FastIntSet) Copy() FastIntSet {
170+
c := FastIntSet{}
171+
c.small = s.small
172+
if s.large != nil {
173+
c.large = new(intsets.Sparse)
174+
c.large.Copy(s.large)
175+
}
176+
return c
177+
}
178+
179+
// CopyFrom clear the receiver to be a copy of the param.
180+
func (s *FastIntSet) CopyFrom(target FastIntSet) {
181+
s.small = target.small
182+
if target.large != nil {
183+
if s.large == nil {
184+
s.large = new(intsets.Sparse)
185+
}
186+
s.large.Copy(target.large)
187+
} else {
188+
if s.large != nil {
189+
s.large.Clear()
190+
}
191+
}
192+
}
193+
194+
// Equals returns whether two int-set are identical.
195+
func (s FastIntSet) Equals(rhs FastIntSet) bool {
196+
if s.large == nil && rhs.large == nil {
197+
return s.small == rhs.small
198+
}
199+
if s.large != nil && rhs.large != nil {
200+
return s.large.Equals(rhs.large)
201+
}
202+
// how come to this? eg: a set operates like: {insert:1, insert:65, remove:65}, resulting a large int-set with only small numbers.
203+
// so we need calculate the exact numbers.
204+
var excess bool
205+
s1 := s.small
206+
s2 := rhs.small
207+
if s.large != nil {
208+
s1, excess = s.largeToSmall()
209+
} else {
210+
s2, excess = rhs.largeToSmall()
211+
}
212+
return !excess && s1 == s2
213+
}
214+
215+
func (s FastIntSet) largeToSmall() (small uint64, otherValues bool) {
216+
if s.large == nil {
217+
panic("set contains no large")
218+
}
219+
return s.small, s.large.Min() < 0 || s.large.Max() >= smallCutOff
220+
}
221+
222+
// *************************************************************************
223+
// * Logic Operators *
224+
// *************************************************************************
225+
226+
// Difference is used to return the s without elements in rhs.
227+
func (s FastIntSet) Difference(rhs FastIntSet) FastIntSet {
228+
r := s.Copy()
229+
r.DifferenceWith(rhs)
230+
return r
231+
}
232+
233+
// DifferenceWith removes any elements in rhs from source.
234+
func (s *FastIntSet) DifferenceWith(rhs FastIntSet) {
235+
s.small &^= rhs.small
236+
if s.large == nil {
237+
return
238+
}
239+
s.large.DifferenceWith(rhs.toLarge())
240+
}
241+
242+
// Union is used to return a union of s and rhs as new set.
243+
func (s FastIntSet) Union(rhs FastIntSet) FastIntSet {
244+
cps := s.Copy()
245+
cps.UnionWith(rhs)
246+
return cps
247+
}
248+
249+
// UnionWith is used to copy all the elements of rhs to source.
250+
func (s *FastIntSet) UnionWith(rhs FastIntSet) {
251+
s.small |= rhs.small
252+
if s.large == nil && rhs.large == nil {
253+
return
254+
}
255+
if s.large == nil {
256+
s.large = s.toLarge()
257+
}
258+
if rhs.large == nil {
259+
for i, ok := rhs.Next(0); ok; i, ok = rhs.Next(i + 1) {
260+
s.large.Insert(i)
261+
}
262+
} else {
263+
s.large.UnionWith(rhs.large)
264+
}
265+
}
266+
267+
// Intersection is used to return the intersection of s and rhs.
268+
func (s FastIntSet) Intersection(rhs FastIntSet) FastIntSet {
269+
r := s.Copy()
270+
r.IntersectionWith(rhs)
271+
return r
272+
}
273+
274+
// IntersectionWith removes any elements not in rhs from source.
275+
func (s *FastIntSet) IntersectionWith(rhs FastIntSet) {
276+
s.small &= rhs.small
277+
if rhs.large == nil {
278+
s.large = nil
279+
}
280+
if s.large == nil {
281+
return
282+
}
283+
s.large.IntersectionWith(rhs.toLarge())
284+
}
285+
286+
// Intersects is used to judge whether two set has something in common.
287+
func (s FastIntSet) Intersects(rhs FastIntSet) bool {
288+
if (s.small & rhs.small) != 0 {
289+
return true
290+
}
291+
if s.large == nil || rhs.large == nil {
292+
return false
293+
}
294+
return s.large.Intersects(rhs.toLarge())
295+
}
296+
297+
// SubsetOf is used to judge whether rhs contains source set.
298+
func (s FastIntSet) SubsetOf(rhs FastIntSet) bool {
299+
if s.large == nil {
300+
return (s.small & rhs.small) == s.small
301+
}
302+
if s.large != nil && rhs.large != nil {
303+
return s.large.SubsetOf(rhs.large)
304+
}
305+
// s is large and rhs is small.
306+
if _, excess := s.largeToSmall(); excess {
307+
// couldn't map s to small.
308+
return false
309+
}
310+
// how come to this? eg: a set operates like: {insert:1, insert:65, remove:65}, resulting a large
311+
// int-set with only small numbers.
312+
return (s.small & rhs.small) == s.small
313+
}
314+
315+
// Shift generates a new set which contains elements i+delta for elements i in
316+
// the original set.
317+
func (s *FastIntSet) Shift(delta int) FastIntSet {
318+
if s.large == nil {
319+
// Fast path.
320+
if delta > 0 {
321+
if bits.LeadingZeros64(s.small)-(64-smallCutOff) >= delta {
322+
return FastIntSet{small: s.small << uint32(delta)}
323+
}
324+
} else {
325+
if bits.TrailingZeros64(s.small) >= -delta {
326+
return FastIntSet{small: s.small >> uint32(-delta)}
327+
}
328+
}
329+
}
330+
// Do the slow thing.
331+
var result FastIntSet
332+
s.ForEach(func(i int) {
333+
result.Insert(i + delta)
334+
})
335+
return result
336+
}
337+
338+
// AddRange adds the interval [from, to] to the Set.
339+
func (s *FastIntSet) AddRange(from, to int) {
340+
if to < from {
341+
panic("invalid range when adding range to FastIntSet")
342+
}
343+
344+
withinSmallBounds := from >= 0 && to < smallCutOff
345+
if withinSmallBounds && s.large == nil {
346+
nValues := to - from + 1
347+
s.small |= (1<<uint64(nValues) - 1) << uint64(from)
348+
} else {
349+
for i := from; i <= to; i++ {
350+
s.Insert(i)
351+
}
352+
}
353+
}
354+
355+
func (s FastIntSet) String() string {
356+
var buf bytes.Buffer
357+
buf.WriteByte('(')
358+
appendRange := func(start, end int) {
359+
if buf.Len() > 1 {
360+
buf.WriteByte(',')
361+
}
362+
if start == end {
363+
fmt.Fprintf(&buf, "%d", start)
364+
} else if start+1 == end {
365+
fmt.Fprintf(&buf, "%d,%d", start, end)
366+
} else {
367+
fmt.Fprintf(&buf, "%d-%d", start, end)
368+
}
369+
}
370+
rangeStart, rangeEnd := -1, -1
371+
s.ForEach(func(i int) {
372+
if i < 0 {
373+
appendRange(i, i)
374+
return
375+
}
376+
if rangeStart != -1 && rangeEnd == i-1 {
377+
rangeEnd = i
378+
} else {
379+
if rangeStart != -1 {
380+
appendRange(rangeStart, rangeEnd)
381+
}
382+
rangeStart, rangeEnd = i, i
383+
}
384+
})
385+
if rangeStart != -1 {
386+
appendRange(rangeStart, rangeEnd)
387+
}
388+
buf.WriteByte(')')
389+
return buf.String()
390+
}

0 commit comments

Comments
 (0)