Skip to content

Commit 64d4d28

Browse files
Use AVX2 for bitmap AND fallback stores
1 parent 672173d commit 64d4d28

4 files changed

Lines changed: 179 additions & 3 deletions

File tree

bitsetops_avx512_amd64.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ func andCardSliceAVX512(dst, a, b []uint64) uint64
3030

3131
// useAVX512BitsetOps requires AVX512_VPOPCNTDQ because the fused kernels use
3232
// VPOPCNTQ; the plain writes only need AVX512F, but they are gated together so
33-
// a single flag governs the whole file. x/sys/cpu verifies operating-system
34-
// support for the ZMM state and honors GODEBUG=cpu.avx512vpopcntdq=off.
33+
// a single flag governs the whole file. When AVX-512 is unavailable, the AND
34+
// paths use the AVX2 store helpers before falling back to Go. x/sys/cpu verifies
35+
// operating-system support for the ZMM state and honors
36+
// GODEBUG=cpu.avx512vpopcntdq=off.
3537
var useAVX512BitsetOps = cpu.X86.HasAVX512VPOPCNTDQ
3638

3739
func orSlice(dst, a, b []uint64) {
@@ -47,6 +49,10 @@ func andSlice(dst, a, b []uint64) {
4749
andSliceAVX512(dst, a, b)
4850
return
4951
}
52+
if useAVX2 {
53+
_andStoreSliceAVX2(dst, a, b)
54+
return
55+
}
5056
andSliceGo(dst, a, b)
5157
}
5258

@@ -80,6 +86,9 @@ func andCardSlice(dst, a, b []uint64) uint64 {
8086
if useAVX512BitsetOps {
8187
return andCardSliceAVX512(dst, a, b)
8288
}
89+
if useAVX2 {
90+
return _andCardStoreSliceAVX2(dst, a, b)
91+
}
8392
andSliceGo(dst, a, b)
8493
return popcntSlice(dst)
8594
}

popcnt_avx2_amd64.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ package roaring
77
// They are only used when the CPU supports AVX2 (see useAVX2); otherwise the
88
// pure-Go fallbacks in popcnt_slices.go are used. This keeps behavior identical
99
// on every target: appengine and non-amd64 builds compile popcnt_generic.go
10-
// instead, and amd64 CPUs without AVX2 take the scalar path at runtime.
10+
// instead, and amd64 CPUs without AVX2 take the scalar path at runtime. The
11+
// AND store helpers are also selected by the bitmap word-operation dispatch.
1112

1213
//go:noescape
1314
func _hasAVX2() bool
@@ -21,6 +22,12 @@ func _popcntMaskSliceAVX2(s, m []uint64) uint64
2122
//go:noescape
2223
func _popcntAndSliceAVX2(s, m []uint64) uint64
2324

25+
//go:noescape
26+
func _andStoreSliceAVX2(dst, a, b []uint64)
27+
28+
//go:noescape
29+
func _andCardStoreSliceAVX2(dst, a, b []uint64) uint64
30+
2431
//go:noescape
2532
func _popcntOrSliceAVX2(s, m []uint64) uint64
2633

popcnt_avx2_amd64.s

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,86 @@ TEXT ·_hasAVX2(SB), NOSPLIT, $0-1
358358
noavx2:
359359
SETEQ ret+0(FP) // ZF is still set by whichever TESTL ran last
360360
RET
361+
362+
// func _andStoreSliceAVX2(dst, a, b []uint64)
363+
// Writes a[i] & b[i] to dst[i]. The three slices must have equal lengths.
364+
// Four uint64 words are processed per AVX2 iteration; a scalar tail handles
365+
// any remaining words. dst may alias either input.
366+
TEXT ·_andStoreSliceAVX2(SB), NOSPLIT, $0-72
367+
MOVQ dst_base+0(FP), DI
368+
MOVQ a_base+24(FP), SI
369+
MOVQ b_base+48(FP), DX
370+
MOVQ dst_len+8(FP), CX
371+
372+
MOVQ CX, R8
373+
SHRQ $2, R8
374+
JZ andstoretail
375+
andstoreloop:
376+
VMOVDQU (SI), Y0
377+
VPAND (DX), Y0, Y0
378+
VMOVDQU Y0, (DI)
379+
ADDQ $32, DI
380+
ADDQ $32, SI
381+
ADDQ $32, DX
382+
DECQ R8
383+
JNZ andstoreloop
384+
andstoretail:
385+
ANDL $3, CX
386+
JZ andstoredone
387+
andstoretailloop:
388+
MOVQ (SI), AX
389+
ANDQ (DX), AX
390+
MOVQ AX, (DI)
391+
ADDQ $8, DI
392+
ADDQ $8, SI
393+
ADDQ $8, DX
394+
DECL CX
395+
JNZ andstoretailloop
396+
andstoredone:
397+
VZEROUPPER
398+
RET
399+
400+
// func _andCardStoreSliceAVX2(dst, a, b []uint64) uint64
401+
// Writes a[i] & b[i] to dst[i] and returns the population count of the
402+
// result. Four uint64 words are processed per AVX2 iteration; a scalar tail
403+
// handles any remaining words. dst may alias either input.
404+
TEXT ·_andCardStoreSliceAVX2(SB), NOSPLIT, $0-80
405+
MOVQ dst_base+0(FP), R10
406+
MOVQ a_base+24(FP), SI
407+
MOVQ b_base+48(FP), DI
408+
MOVQ dst_len+8(FP), CX
409+
XORL AX, AX
410+
411+
MOVQ CX, R8
412+
SHRQ $2, R8
413+
JZ andcardstoretail
414+
SETUP
415+
andcardstoreloop:
416+
VMOVDQU (SI), Ydata
417+
VPAND (DI), Ydata, Ydata
418+
VMOVDQU Ydata, (R10)
419+
COUNTBLOCK
420+
ADDQ $32, R10
421+
ADDQ $32, SI
422+
ADDQ $32, DI
423+
DECQ R8
424+
JNZ andcardstoreloop
425+
HSUM
426+
andcardstoretail:
427+
ANDL $3, CX
428+
JZ andcardstoredone
429+
andcardstoretailloop:
430+
MOVQ (SI), DX
431+
ANDQ (DI), DX
432+
MOVQ DX, (R10)
433+
POPCNTQ DX, DX
434+
ADDQ DX, AX
435+
ADDQ $8, R10
436+
ADDQ $8, SI
437+
ADDQ $8, DI
438+
DECL CX
439+
JNZ andcardstoretailloop
440+
andcardstoredone:
441+
VZEROUPPER
442+
MOVQ AX, ret+72(FP)
443+
RET

popcnt_avx2_amd64_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,80 @@ func TestAVX2PopcntDifferential(t *testing.T) {
127127
}
128128
}
129129
}
130+
131+
func TestAVX2AndStoreDifferential(t *testing.T) {
132+
if !useAVX2 {
133+
t.Skip("AVX2 not available on this CPU")
134+
}
135+
r := rand.New(rand.NewSource(43))
136+
for _, n := range avx2TestLengths {
137+
for iter := 0; iter < 64; iter++ {
138+
a := randomUint64Slice(r, n)
139+
b := randomUint64Slice(r, n)
140+
want := make([]uint64, n)
141+
andSliceGo(want, a, b)
142+
143+
got := make([]uint64, n)
144+
_andStoreSliceAVX2(got, a, b)
145+
assert.Equalf(t, want, got, "separate destination len=%d", n)
146+
147+
aliasA := append(make([]uint64, 0, n), a...)
148+
_andStoreSliceAVX2(aliasA, aliasA, b)
149+
assert.Equalf(t, want, aliasA, "destination aliases a len=%d", n)
150+
151+
aliasB := append(make([]uint64, 0, n), b...)
152+
_andStoreSliceAVX2(aliasB, a, aliasB)
153+
assert.Equalf(t, want, aliasB, "destination aliases b len=%d", n)
154+
155+
wantCard := andCardSliceGo(want, a, b)
156+
gotCard := make([]uint64, n)
157+
card := _andCardStoreSliceAVX2(gotCard, a, b)
158+
assert.Equalf(t, want, gotCard, "fused destination len=%d", n)
159+
assert.Equalf(t, wantCard, card, "fused cardinality len=%d", n)
160+
161+
aliasA = append(make([]uint64, 0, n), a...)
162+
card = _andCardStoreSliceAVX2(aliasA, aliasA, b)
163+
assert.Equalf(t, want, aliasA, "fused destination aliases a len=%d", n)
164+
assert.Equalf(t, wantCard, card, "fused aliases a cardinality len=%d", n)
165+
166+
aliasB = append(make([]uint64, 0, n), b...)
167+
card = _andCardStoreSliceAVX2(aliasB, a, aliasB)
168+
assert.Equalf(t, want, aliasB, "fused destination aliases b len=%d", n)
169+
assert.Equalf(t, wantCard, card, "fused aliases b cardinality len=%d", n)
170+
}
171+
}
172+
}
173+
174+
func TestAVX2AndStoreDispatch(t *testing.T) {
175+
if !useAVX2 {
176+
t.Skip("AVX2 not available on this CPU")
177+
}
178+
savedAVX512 := useAVX512BitsetOps
179+
savedAVX512Popcnt := useAVX512Popcnt
180+
savedAVX2 := useAVX2
181+
defer func() {
182+
useAVX512BitsetOps = savedAVX512
183+
useAVX512Popcnt = savedAVX512Popcnt
184+
useAVX2 = savedAVX2
185+
}()
186+
useAVX512BitsetOps = false
187+
useAVX512Popcnt = false
188+
useAVX2 = true
189+
190+
r := rand.New(rand.NewSource(44))
191+
for _, n := range avx2TestLengths {
192+
a := randomUint64Slice(r, n)
193+
b := randomUint64Slice(r, n)
194+
want := make([]uint64, n)
195+
andSliceGo(want, a, b)
196+
got := make([]uint64, n)
197+
andSlice(got, a, b)
198+
assert.Equalf(t, want, got, "andSlice AVX2 dispatch len=%d", n)
199+
200+
gotCard := make([]uint64, n)
201+
wantCard := andCardSliceGo(want, a, b)
202+
card := andCardSlice(gotCard, a, b)
203+
assert.Equalf(t, want, gotCard, "andCardSlice AVX2 dispatch len=%d", n)
204+
assert.Equalf(t, wantCard, card, "andCardSlice AVX2 cardinality len=%d", n)
205+
}
206+
}

0 commit comments

Comments
 (0)