Skip to content

Commit 90c3cf4

Browse files
RaduBerindebradfitz
authored andcommitted
hash/crc32: improve the AMD64 implementation using SSE4.2
The algorithm is explained in the comments. The improvement in throughput is about 1.4x for buffers between 500b-4Kb and 2.5x-2.6x for larger buffers. Additionally, we no longer initialize the software tables if SSE4.2 is available. Adding a test for the SSE implementation (restricted to amd64 and amd64p32). Benchmarks on a Haswell i5-4670 @ 3.4 GHz: name old time/op new time/op delta CastagnoliCrc15B-4 21.9ns ± 1% 22.9ns ± 0% +4.45% CastagnoliCrc15BMisaligned-4 22.6ns ± 0% 23.4ns ± 0% +3.43% CastagnoliCrc40B-4 23.3ns ± 0% 23.9ns ± 0% +2.58% CastagnoliCrc40BMisaligned-4 25.4ns ± 0% 26.1ns ± 0% +2.86% CastagnoliCrc512-4 72.6ns ± 0% 52.8ns ± 0% -27.33% CastagnoliCrc512Misaligned-4 76.3ns ± 1% 56.3ns ± 0% -26.18% CastagnoliCrc1KB-4 128ns ± 1% 89ns ± 0% -30.04% CastagnoliCrc1KBMisaligned-4 130ns ± 0% 88ns ± 0% -32.65% CastagnoliCrc4KB-4 461ns ± 0% 187ns ± 0% -59.40% CastagnoliCrc4KBMisaligned-4 463ns ± 0% 191ns ± 0% -58.77% CastagnoliCrc32KB-4 3.58µs ± 0% 1.35µs ± 0% -62.22% CastagnoliCrc32KBMisaligned-4 3.58µs ± 0% 1.36µs ± 0% -61.84% name old speed new speed delta CastagnoliCrc15B-4 684MB/s ± 1% 655MB/s ± 0% -4.32% CastagnoliCrc15BMisaligned-4 663MB/s ± 0% 641MB/s ± 0% -3.32% CastagnoliCrc40B-4 1.72GB/s ± 0% 1.67GB/s ± 0% -2.69% CastagnoliCrc40BMisaligned-4 1.58GB/s ± 0% 1.53GB/s ± 0% -2.82% CastagnoliCrc512-4 7.05GB/s ± 0% 9.70GB/s ± 0% +37.59% CastagnoliCrc512Misaligned-4 6.71GB/s ± 1% 9.09GB/s ± 0% +35.43% CastagnoliCrc1KB-4 7.98GB/s ± 1% 11.46GB/s ± 0% +43.55% CastagnoliCrc1KBMisaligned-4 7.86GB/s ± 0% 11.70GB/s ± 0% +48.75% CastagnoliCrc4KB-4 8.87GB/s ± 0% 21.80GB/s ± 0% +145.69% CastagnoliCrc4KBMisaligned-4 8.83GB/s ± 0% 21.39GB/s ± 0% +142.25% CastagnoliCrc32KB-4 9.15GB/s ± 0% 24.22GB/s ± 0% +164.62% CastagnoliCrc32KBMisaligned-4 9.16GB/s ± 0% 24.00GB/s ± 0% +161.94% Fixes #16107. Change-Id: Ibe50ea76574674ce0571ef31c31015e0ed66b907 Reviewed-on: https://go-review.googlesource.com/27931 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 320bd56 commit 90c3cf4

8 files changed

+301
-14
lines changed

src/hash/crc32/crc32.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,14 @@ var castagnoliTable8 *slicing8Table
5252
var castagnoliOnce sync.Once
5353

5454
func castagnoliInit() {
55-
castagnoliTable = makeTable(Castagnoli)
56-
castagnoliTable8 = makeTable8(Castagnoli)
55+
// Call the arch-specific init function and let it decide if we will need
56+
// the tables for the generic implementation.
57+
needGenericTables := castagnoliInitArch()
58+
59+
if needGenericTables {
60+
castagnoliTable = makeTable(Castagnoli)
61+
castagnoliTable8 = makeTable8(Castagnoli)
62+
}
5763
}
5864

5965
// IEEETable is the table for the IEEE polynomial.

src/hash/crc32/crc32_amd64.go

Lines changed: 163 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package crc32
66

7+
import "unsafe"
8+
79
// This file contains the code to call the SSE 4.2 version of the Castagnoli
810
// and IEEE CRC.
911

@@ -13,11 +15,20 @@ func haveSSE41() bool
1315
func haveSSE42() bool
1416
func haveCLMUL() bool
1517

16-
// castagnoliSSE42 is defined in crc_amd64.s and uses the SSE4.2 CRC32
18+
// castagnoliSSE42 is defined in crc32_amd64.s and uses the SSE4.2 CRC32
1719
// instruction.
1820
//go:noescape
1921
func castagnoliSSE42(crc uint32, p []byte) uint32
2022

23+
// castagnoliSSE42Triple is defined in crc32_amd64.s and uses the SSE4.2 CRC32
24+
// instruction.
25+
//go:noescape
26+
func castagnoliSSE42Triple(
27+
crcA, crcB, crcC uint32,
28+
a, b, c []byte,
29+
rounds uint32,
30+
) (retA uint32, retB uint32, retC uint32)
31+
2132
// ieeeCLMUL is defined in crc_amd64.s and uses the PCLMULQDQ
2233
// instruction as well as SSE 4.1.
2334
//go:noescape
@@ -26,15 +37,160 @@ func ieeeCLMUL(crc uint32, p []byte) uint32
2637
var sse42 = haveSSE42()
2738
var useFastIEEE = haveCLMUL() && haveSSE41()
2839

40+
const castagnoliK1 = 168
41+
const castagnoliK2 = 1344
42+
43+
type sse42Table [4]Table
44+
45+
var castagnoliSSE42TableK1 *sse42Table
46+
var castagnoliSSE42TableK2 *sse42Table
47+
48+
func castagnoliInitArch() (needGenericTables bool) {
49+
if !sse42 {
50+
return true
51+
}
52+
castagnoliSSE42TableK1 = new(sse42Table)
53+
castagnoliSSE42TableK2 = new(sse42Table)
54+
// See description in updateCastagnoli.
55+
// t[0][i] = CRC(i000, O)
56+
// t[1][i] = CRC(0i00, O)
57+
// t[2][i] = CRC(00i0, O)
58+
// t[3][i] = CRC(000i, O)
59+
// where O is a sequence of K zeros.
60+
var tmp [castagnoliK2]byte
61+
for b := 0; b < 4; b++ {
62+
for i := 0; i < 256; i++ {
63+
val := uint32(i) << uint32(b*8)
64+
castagnoliSSE42TableK1[b][i] = castagnoliSSE42(val, tmp[:castagnoliK1])
65+
castagnoliSSE42TableK2[b][i] = castagnoliSSE42(val, tmp[:])
66+
}
67+
}
68+
return false
69+
}
70+
71+
// castagnoliShift computes the CRC32-C of K1 or K2 zeroes (depending on the
72+
// table given) with the given initial crc value. This corresponds to
73+
// CRC(crc, O) in the description in updateCastagnoli.
74+
func castagnoliShift(table *sse42Table, crc uint32) uint32 {
75+
return table[3][crc>>24] ^
76+
table[2][(crc>>16)&0xFF] ^
77+
table[1][(crc>>8)&0xFF] ^
78+
table[0][crc&0xFF]
79+
}
80+
2981
func updateCastagnoli(crc uint32, p []byte) uint32 {
30-
if sse42 {
31-
return castagnoliSSE42(crc, p)
82+
if !sse42 {
83+
// Use slicing-by-8 on larger inputs.
84+
if len(p) >= sliceBy8Cutoff {
85+
return updateSlicingBy8(crc, castagnoliTable8, p)
86+
}
87+
return update(crc, castagnoliTable, p)
3288
}
33-
// Use slicing-by-8 on larger inputs.
34-
if len(p) >= sliceBy8Cutoff {
35-
return updateSlicingBy8(crc, castagnoliTable8, p)
89+
90+
// This method is inspired from the algorithm in Intel's white paper:
91+
// "Fast CRC Computation for iSCSI Polynomial Using CRC32 Instruction"
92+
// The same strategy of splitting the buffer in three is used but the
93+
// combining calculation is different; the complete derivation is explained
94+
// below.
95+
//
96+
// -- The basic idea --
97+
//
98+
// The CRC32 instruction (available in SSE4.2) can process 8 bytes at a
99+
// time. In recent Intel architectures the instruction takes 3 cycles;
100+
// however the processor can pipeline up to three instructions if they
101+
// don't depend on each other.
102+
//
103+
// Roughly this means that we can process three buffers in about the same
104+
// time we can process one buffer.
105+
//
106+
// The idea is then to split the buffer in three, CRC the three pieces
107+
// separately and then combine the results.
108+
//
109+
// Combining the results requires precomputed tables, so we must choose a
110+
// fixed buffer length to optimize. The longer the length, the faster; but
111+
// only buffers longer than this length will use the optimization. We choose
112+
// two cutoffs and compute tables for both:
113+
// - one around 512: 168*3=504
114+
// - one around 4KB: 1344*3=4032
115+
//
116+
// -- The nitty gritty --
117+
//
118+
// Let CRC(I, X) be the non-inverted CRC32-C of the sequence X (with
119+
// initial non-inverted CRC I). This function has the following properties:
120+
// (a) CRC(I, AB) = CRC(CRC(I, A), B)
121+
// (b) CRC(I, A xor B) = CRC(I, A) xor CRC(0, B)
122+
//
123+
// Say we want to compute CRC(I, ABC) where A, B, C are three sequences of
124+
// K bytes each, where K is a fixed constant. Let O be the sequence of K zero
125+
// bytes.
126+
//
127+
// CRC(I, ABC) = CRC(I, ABO xor C)
128+
// = CRC(I, ABO) xor CRC(0, C)
129+
// = CRC(CRC(I, AB), O) xor CRC(0, C)
130+
// = CRC(CRC(I, AO xor B), O) xor CRC(0, C)
131+
// = CRC(CRC(I, AO) xor CRC(0, B), O) xor CRC(0, C)
132+
// = CRC(CRC(CRC(I, A), O) xor CRC(0, B), O) xor CRC(0, C)
133+
//
134+
// The castagnoliSSE42Triple function can compute CRC(I, A), CRC(0, B),
135+
// and CRC(0, C) efficiently. We just need to find a way to quickly compute
136+
// CRC(uvwx, O) given a 4-byte initial value uvwx. We can precompute these
137+
// values; since we can't have a 32-bit table, we break it up into four
138+
// 8-bit tables:
139+
//
140+
// CRC(uvwx, O) = CRC(u000, O) xor
141+
// CRC(0v00, O) xor
142+
// CRC(00w0, O) xor
143+
// CRC(000x, O)
144+
//
145+
// We can compute tables corresponding to the four terms for all 8-bit
146+
// values.
147+
148+
crc = ^crc
149+
150+
// If a buffer is long enough to use the optimization, process the first few
151+
// bytes to align the buffer to an 8 byte boundary (if necessary).
152+
if len(p) >= castagnoliK1*3 {
153+
delta := int(uintptr(unsafe.Pointer(&p[0])) & 7)
154+
if delta != 0 {
155+
delta = 8 - delta
156+
crc = castagnoliSSE42(crc, p[:delta])
157+
p = p[delta:]
158+
}
36159
}
37-
return update(crc, castagnoliTable, p)
160+
161+
// Process 3*K2 at a time.
162+
for len(p) >= castagnoliK2*3 {
163+
// Compute CRC(I, A), CRC(0, B), and CRC(0, C).
164+
crcA, crcB, crcC := castagnoliSSE42Triple(
165+
crc, 0, 0,
166+
p, p[castagnoliK2:], p[castagnoliK2*2:],
167+
castagnoliK2/24)
168+
169+
// CRC(I, AB) = CRC(CRC(I, A), O) xor CRC(0, B)
170+
crcAB := castagnoliShift(castagnoliSSE42TableK2, crcA) ^ crcB
171+
// CRC(I, ABC) = CRC(CRC(I, AB), O) xor CRC(0, C)
172+
crc = castagnoliShift(castagnoliSSE42TableK2, crcAB) ^ crcC
173+
p = p[castagnoliK2*3:]
174+
}
175+
176+
// Process 3*K1 at a time.
177+
for len(p) >= castagnoliK1*3 {
178+
// Compute CRC(I, A), CRC(0, B), and CRC(0, C).
179+
crcA, crcB, crcC := castagnoliSSE42Triple(
180+
crc, 0, 0,
181+
p, p[castagnoliK1:], p[castagnoliK1*2:],
182+
castagnoliK1/24)
183+
184+
// CRC(I, AB) = CRC(CRC(I, A), O) xor CRC(0, B)
185+
crcAB := castagnoliShift(castagnoliSSE42TableK1, crcA) ^ crcB
186+
// CRC(I, ABC) = CRC(CRC(I, AB), O) xor CRC(0, C)
187+
crc = castagnoliShift(castagnoliSSE42TableK1, crcAB) ^ crcC
188+
p = p[castagnoliK1*3:]
189+
}
190+
191+
// Use the simple implementation for what's left.
192+
crc = castagnoliSSE42(crc, p)
193+
return ^crc
38194
}
39195

40196
func updateIEEE(crc uint32, p []byte) uint32 {

src/hash/crc32/crc32_amd64.s

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
#include "textflag.h"
66

7+
// castagnoliSSE42 updates the (non-inverted) crc with the given buffer.
8+
//
79
// func castagnoliSSE42(crc uint32, p []byte) uint32
810
TEXT ·castagnoliSSE42(SB),NOSPLIT,$0
911
MOVL crc+0(FP), AX // CRC value
1012
MOVQ p+8(FP), SI // data pointer
1113
MOVQ p_len+16(FP), CX // len(p)
1214

13-
NOTL AX
14-
1515
// If there are fewer than 8 bytes to process, skip alignment.
1616
CMPQ CX, $8
1717
JL less_than_8
@@ -87,10 +87,53 @@ less_than_2:
8787
CRC32B (SI), AX
8888

8989
done:
90-
NOTL AX
9190
MOVL AX, ret+32(FP)
9291
RET
9392

93+
// castagnoliSSE42Triple updates three (non-inverted) crcs with (24*rounds)
94+
// bytes from each buffer.
95+
//
96+
// func castagnoliSSE42Triple(
97+
// crc1, crc2, crc3 uint32,
98+
// a, b, c []byte,
99+
// rounds uint32,
100+
// ) (retA uint32, retB uint32, retC uint32)
101+
TEXT ·castagnoliSSE42Triple(SB),NOSPLIT,$0
102+
MOVL crcA+0(FP), AX
103+
MOVL crcB+4(FP), CX
104+
MOVL crcC+8(FP), DX
105+
106+
MOVQ a+16(FP), R8 // data pointer
107+
MOVQ b+40(FP), R9 // data pointer
108+
MOVQ c+64(FP), R10 // data pointer
109+
110+
MOVL rounds+88(FP), R11
111+
112+
loop:
113+
CRC32Q (R8), AX
114+
CRC32Q (R9), CX
115+
CRC32Q (R10), DX
116+
117+
CRC32Q 8(R8), AX
118+
CRC32Q 8(R9), CX
119+
CRC32Q 8(R10), DX
120+
121+
CRC32Q 16(R8), AX
122+
CRC32Q 16(R9), CX
123+
CRC32Q 16(R10), DX
124+
125+
ADDQ $24, R8
126+
ADDQ $24, R9
127+
ADDQ $24, R10
128+
129+
DECQ R11
130+
JNZ loop
131+
132+
MOVL AX, retA+96(FP)
133+
MOVL CX, retB+100(FP)
134+
MOVL DX, retC+104(FP)
135+
RET
136+
94137
// func haveSSE42() bool
95138
TEXT ·haveSSE42(SB),NOSPLIT,$0
96139
XORQ AX, AX

src/hash/crc32/crc32_amd64_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2009 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package crc32
6+
7+
import (
8+
"math/rand"
9+
"testing"
10+
)
11+
12+
func TestCastagnoliSSE42(t *testing.T) {
13+
if !sse42 {
14+
t.Skip("SSE42 not supported")
15+
}
16+
17+
// Init the SSE42 tables.
18+
MakeTable(Castagnoli)
19+
20+
// Manually init the software implementation to compare against.
21+
castagnoliTable = makeTable(Castagnoli)
22+
castagnoliTable8 = makeTable8(Castagnoli)
23+
24+
// The optimized SSE4.2 implementation behaves differently for different
25+
// lengths (especially around multiples of K*3). Crosscheck against the
26+
// software implementation for various lengths.
27+
for _, base := range []int{castagnoliK1, castagnoliK2, castagnoliK1 + castagnoliK2} {
28+
for _, baseMult := range []int{2, 3, 5, 6, 9, 30} {
29+
for _, variation := range []int{0, 1, 2, 3, 4, 7, 10, 16, 32, 50, 128} {
30+
for _, varMult := range []int{-2, -1, +1, +2} {
31+
length := base*baseMult + variation*varMult
32+
p := make([]byte, length)
33+
_, _ = rand.Read(p)
34+
crcInit := uint32(rand.Int63())
35+
correct := updateSlicingBy8(crcInit, castagnoliTable8, p)
36+
result := updateCastagnoli(crcInit, p)
37+
if result != correct {
38+
t.Errorf("SSE42 implementation = 0x%x want 0x%x (buffer length %d)",
39+
result, correct, len(p))
40+
}
41+
}
42+
}
43+
}
44+
}
45+
}

src/hash/crc32/crc32_amd64p32.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,22 @@ package crc32
77
// This file contains the code to call the SSE 4.2 version of the Castagnoli
88
// CRC.
99

10-
// haveSSE42 is defined in crc_amd64p32.s and uses CPUID to test for SSE 4.2
10+
// haveSSE42 is defined in crc32_amd64p32.s and uses CPUID to test for SSE 4.2
1111
// support.
1212
func haveSSE42() bool
1313

14-
// castagnoliSSE42 is defined in crc_amd64.s and uses the SSE4.2 CRC32
14+
// castagnoliSSE42 is defined in crc32_amd64.s and uses the SSE4.2 CRC32
1515
// instruction.
1616
//go:noescape
1717
func castagnoliSSE42(crc uint32, p []byte) uint32
1818

1919
var sse42 = haveSSE42()
2020

21+
func castagnoliInitArch() (needGenericTables bool) {
22+
// We only need the generic implementation tables if we don't have SSE4.2.
23+
return !sse42
24+
}
25+
2126
func updateCastagnoli(crc uint32, p []byte) uint32 {
2227
if sse42 {
2328
return castagnoliSSE42(crc, p)

src/hash/crc32/crc32_generic.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ package crc32
99
// This file contains the generic version of updateCastagnoli which does
1010
// slicing-by-8, or uses the fallback for very small sizes.
1111

12+
func castagnoliInitArch() (needGenericTables bool) {
13+
return true
14+
}
15+
1216
func updateCastagnoli(crc uint32, p []byte) uint32 {
1317
// Use slicing-by-8 on larger inputs.
1418
if len(p) >= sliceBy8Cutoff {

src/hash/crc32/crc32_s390x.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ func vectorizedCastagnoli(crc uint32, p []byte) uint32
2525
//go:noescape
2626
func vectorizedIEEE(crc uint32, p []byte) uint32
2727

28+
func castagnoliInitArch() (needGenericTables bool) {
29+
return true
30+
}
31+
2832
func genericCastagnoli(crc uint32, p []byte) uint32 {
2933
// Use slicing-by-8 on larger inputs.
3034
if len(p) >= sliceBy8Cutoff {

0 commit comments

Comments
 (0)