Skip to content

Commit 854612b

Browse files
committed
Implement shotgun4Intersect
1 parent 988cb10 commit 854612b

2 files changed

Lines changed: 168 additions & 9 deletions

File tree

setutil.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,104 @@ mainwhile:
582582
return pos
583583
}
584584

585+
// shotgun4Intersect performs intersection between small and large arrays described in
586+
// https://lemire.me/blog/2019/01/16/faster-intersections-between-sorted-arrays-with-shotgun/
587+
func shotgun4Intersect(small, large, buf []uint16) int {
588+
if len(small) == 0 {
589+
return 0
590+
}
591+
592+
nS, nL := len(small), len(large)
593+
buf = buf[:cap(buf)]
594+
idxS, idxL := 0, 0
595+
pos := 0
596+
597+
for (idxS+4 <= nS) && idxL < nL {
598+
t1, t2, t3, t4 := small[idxS], small[idxS+1], small[idxS+2], small[idxS+3]
599+
idx1, idx2, idx3, idx4 := idxL, idxL, idxL, idxL
600+
n := nL - idxL
601+
602+
for n > 1 {
603+
m := n >> 1
604+
605+
if large[idx1+m] < t1 {
606+
idx1 += m
607+
}
608+
609+
if large[idx2+m] < t2 {
610+
idx2 += m
611+
}
612+
613+
if large[idx3+m] < t3 {
614+
idx3 += m
615+
}
616+
617+
if large[idx4+m] < t4 {
618+
idx4 += m
619+
}
620+
621+
n -= m
622+
}
623+
624+
if large[idx1] < t1 {
625+
idx1++
626+
}
627+
628+
if large[idx2] < t2 {
629+
idx2++
630+
}
631+
632+
if large[idx3] < t3 {
633+
idx3++
634+
}
635+
636+
if large[idx4] < t4 {
637+
idx4++
638+
}
639+
640+
if idx1 < nL && large[idx1] == t1 {
641+
buf[pos] = t1
642+
pos++
643+
}
644+
645+
if idx2 < nL && large[idx2] == t2 {
646+
buf[pos] = t2
647+
pos++
648+
}
649+
650+
if idx3 < nL && large[idx3] == t3 {
651+
buf[pos] = t3
652+
pos++
653+
}
654+
655+
if idx4 < nL && large[idx4] == t4 {
656+
buf[pos] = t4
657+
pos++
658+
}
659+
660+
idxS += 4
661+
idxL = idx4
662+
}
663+
664+
for idxS < nS && idxL < nL {
665+
s := small[idxS]
666+
idxL = advanceUntil(large, idxL, nL, s)
667+
668+
if idxL == nL {
669+
break
670+
}
671+
672+
if large[idxL] == s {
673+
buf[pos] = s
674+
pos++
675+
}
676+
677+
idxS++
678+
}
679+
680+
return pos
681+
}
682+
585683
func binarySearch(array []uint16, ikey uint16) int {
586684
low := 0
587685
high := len(array) - 1

setutil_test.go

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package roaring
44

55
import (
66
"github.com/stretchr/testify/assert"
7+
"math/rand"
78
"testing"
89
)
910

@@ -92,16 +93,31 @@ func TestSetUtilIntersection(t *testing.T) {
9293
assert.Equal(t, expectedresult, result)
9394
}
9495

95-
func TestSetUtilIntersection2(t *testing.T) {
96-
data1 := []uint16{0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
97-
data2 := []uint16{0, 3, 6, 9, 12, 15, 18}
98-
result := make([]uint16, 0, len(data1)+len(data2))
99-
expectedresult := []uint16{0, 6, 12, 18}
100-
nl := intersection2by2(data1, data2, result)
101-
result = result[:nl]
102-
result = result[:len(expectedresult)]
96+
func TestSetUtilIntersectionCases(t *testing.T) {
97+
cases := []struct {
98+
name string
99+
algo func(a, b, buf []uint16) int
100+
}{
101+
{
102+
name: "onesidedgallopingintersect2by2",
103+
algo: onesidedgallopingintersect2by2,
104+
},
105+
{
106+
name: "shotgun4Intersect",
107+
algo: shotgun4Intersect,
108+
},
109+
}
103110

104-
assert.Equal(t, expectedresult, result)
111+
data1 := []uint16{0, 3, 6, 9, 12, 15, 18}
112+
data2 := []uint16{0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
113+
expected := []uint16{0, 6, 12, 18}
114+
115+
for _, c := range cases {
116+
result := make([]uint16, 0, len(data1)+len(data2))
117+
n := c.algo(data1, data2, result)
118+
119+
assert.Equalf(t, expected, result[:n], "failed algorithm: %s", c.name)
120+
}
105121
}
106122

107123
func TestSetUtilBinarySearch(t *testing.T) {
@@ -119,3 +135,48 @@ func TestSetUtilBinarySearch(t *testing.T) {
119135
}
120136
}
121137
}
138+
139+
func BenchmarkIntersectAlgorithms(b *testing.B) {
140+
sz1 := 1000
141+
s1 := make([]uint16, sz1)
142+
143+
sz2 := MaxUint16
144+
s2 := make([]uint16, sz2)
145+
146+
for i := 0; i < sz2; i++ {
147+
s2[i] = uint16(i)
148+
}
149+
150+
r := rand.New(rand.NewSource(0))
151+
k := 0
152+
153+
for i := 0; i < sz1 && k < sz2; i++ {
154+
n := r.Intn(100)
155+
k += n
156+
157+
// prevent adding duplicates
158+
if n == 0 && i > 0 {
159+
k++
160+
}
161+
162+
s1[i] = uint16(s2[k])
163+
}
164+
165+
buf := make([]uint16, sz1+sz2)
166+
167+
b.Run("onesidedgallopingintersect2by2", func(b *testing.B) {
168+
b.ResetTimer()
169+
170+
for i := 0; i < b.N; i++ {
171+
onesidedgallopingintersect2by2(s1, s2, buf)
172+
}
173+
})
174+
175+
b.Run("shotgun4", func(b *testing.B) {
176+
b.ResetTimer()
177+
178+
for i := 0; i < b.N; i++ {
179+
shotgun4Intersect(s1, s2, buf)
180+
}
181+
})
182+
}

0 commit comments

Comments
 (0)