diff --git a/go.mod b/go.mod index 49f6760..9885116 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,8 @@ module github.com/cespare/xxhash/v2 go 1.11 + +require ( + github.com/klauspost/cpuid/v2 v2.2.8 + golang.org/x/sys v0.15.0 // indirect +) diff --git a/go.sum b/go.sum index e69de29..01bd722 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,5 @@ +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/xxhash.go b/xxhash.go index 78bddf1..dd188e9 100644 --- a/xxhash.go +++ b/xxhash.go @@ -27,13 +27,10 @@ var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5} // Note that a zero-valued Digest is not ready to receive writes. // Call Reset or create a Digest using New before calling other methods. type Digest struct { - v1 uint64 - v2 uint64 - v3 uint64 - v4 uint64 + s [4]uint64 total uint64 mem [32]byte - n int // how much of mem is used + n uint8 // how much of mem is used } // New creates a new Digest with a zero seed. @@ -57,10 +54,10 @@ func (d *Digest) Reset() { // ResetWithSeed clears the Digest's state so that it can be reused. // It uses the given seed to initialize the state. func (d *Digest) ResetWithSeed(seed uint64) { - d.v1 = seed + prime1 + prime2 - d.v2 = seed + prime2 - d.v3 = seed - d.v4 = seed - prime1 + d.s[0] = seed + prime1 + prime2 + d.s[1] = seed + prime2 + d.s[2] = seed + d.s[3] = seed - prime1 d.total = 0 d.n = 0 } @@ -76,35 +73,32 @@ func (d *Digest) Write(b []byte) (n int, err error) { n = len(b) d.total += uint64(n) - memleft := d.mem[d.n&(len(d.mem)-1):] - - if d.n+n < 32 { - // This new data doesn't even fill the current block. - copy(memleft, b) - d.n += n - return - } - - if d.n > 0 { - // Finish off the partial block. - c := copy(memleft, b) - d.v1 = round(d.v1, u64(d.mem[0:8])) - d.v2 = round(d.v2, u64(d.mem[8:16])) - d.v3 = round(d.v3, u64(d.mem[16:24])) - d.v4 = round(d.v4, u64(d.mem[24:32])) - b = b[c:] + var extra *[32]byte + if d.n != 0 { + // there is data already in mem, append to it. + added := copy(d.mem[d.n:], b) + b = b[added:] + d.n += uint8(added) + if uint(d.n) < uint(len(d.mem)) { + // not enough data to hash. + return + } + extra = &d.mem d.n = 0 } if len(b) >= 32 { // One or more full blocks left. - nw := writeBlocks(d, b) - b = b[nw:] + writeBlocks(d, extra, b) + b = b[uint(len(b))&^31:] + } else if extra != nil { + // we don't have enough data to fill b but we have an extra. + // write blocks must never be called with len(b) < 32 so pass extra as b. + writeBlocks(d, nil, extra[:]) } // Store any remaining partial block. - copy(d.mem[:], b) - d.n = len(b) + d.n = uint8(copy(d.mem[:], b)) return } @@ -130,19 +124,19 @@ func (d *Digest) Sum64() uint64 { var h uint64 if d.total >= 32 { - v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4 + v1, v2, v3, v4 := d.s[0], d.s[1], d.s[2], d.s[3] h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4) h = mergeRound(h, v1) h = mergeRound(h, v2) h = mergeRound(h, v3) h = mergeRound(h, v4) } else { - h = d.v3 + prime5 + h = d.s[2] + prime5 } h += d.total - b := d.mem[:d.n&(len(d.mem)-1)] + b := d.mem[:d.n&uint8(len(d.mem)-1)] for ; len(b) >= 8; b = b[8:] { k1 := round(0, u64(b[:8])) h ^= k1 @@ -176,13 +170,13 @@ const ( func (d *Digest) MarshalBinary() ([]byte, error) { b := make([]byte, 0, marshaledSize) b = append(b, magic...) - b = appendUint64(b, d.v1) - b = appendUint64(b, d.v2) - b = appendUint64(b, d.v3) - b = appendUint64(b, d.v4) + b = appendUint64(b, d.s[0]) + b = appendUint64(b, d.s[1]) + b = appendUint64(b, d.s[2]) + b = appendUint64(b, d.s[3]) b = appendUint64(b, d.total) b = append(b, d.mem[:d.n]...) - b = b[:len(b)+len(d.mem)-d.n] + b = b[:len(b)+len(d.mem)-int(d.n)] return b, nil } @@ -195,13 +189,13 @@ func (d *Digest) UnmarshalBinary(b []byte) error { return errors.New("xxhash: invalid hash state size") } b = b[len(magic):] - b, d.v1 = consumeUint64(b) - b, d.v2 = consumeUint64(b) - b, d.v3 = consumeUint64(b) - b, d.v4 = consumeUint64(b) + b, d.s[0] = consumeUint64(b) + b, d.s[1] = consumeUint64(b) + b, d.s[2] = consumeUint64(b) + b, d.s[3] = consumeUint64(b) b, d.total = consumeUint64(b) copy(d.mem[:], b) - d.n = int(d.total % uint64(len(d.mem))) + d.n = uint8(d.total % uint64(len(d.mem))) return nil } diff --git a/xxhash_amd64.s b/xxhash_amd64.s index 3e8b132..3f00db5 100644 --- a/xxhash_amd64.s +++ b/xxhash_amd64.s @@ -40,11 +40,8 @@ IMULQ prime1, acc \ ADDQ prime4, acc -// blockLoop processes as many 32-byte blocks as possible, -// updating v1, v2, v3, and v4. It assumes that there is at least one block -// to process. -#define blockLoop() \ -loop: \ +// round32 perform a 32byte round loading from ptr on v1, v2, v3, v4. +#define round32() \ MOVQ +0(p), x \ round(v1, x) \ MOVQ +8(p), x \ @@ -52,13 +49,20 @@ loop: \ MOVQ +16(p), x \ round(v3, x) \ MOVQ +24(p), x \ - round(v4, x) \ - ADDQ $32, p \ - CMPQ p, end \ + round(v4, x) + +// blockLoop processes as many 32-byte blocks as possible, +// updating v1, v2, v3, and v4. It assumes that there is at least one block +// to process. +#define blockLoop() \ +loop: \ + round32() \ + ADDQ $32, p \ + CMPQ p, end \ JLE loop -// func Sum64(b []byte) uint64 -TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32 +// func sum64(b []byte) uint64 +TEXT ·sum64Scalar(SB), NOSPLIT|NOFRAME, $0-32 // Load fixed primes. MOVQ ·primes+0(SB), prime1 MOVQ ·primes+8(SB), prime2 @@ -173,18 +177,12 @@ finalize: MOVQ h, ret+24(FP) RET -// func writeBlocks(d *Digest, b []byte) int -TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40 +// func writeBlocksScalar(d *Digest, extra *[32]byte, b []byte) +TEXT ·writeBlocksScalar(SB), NOSPLIT|NOFRAME, $0-40 // Load fixed primes needed for round. MOVQ ·primes+0(SB), prime1 MOVQ ·primes+8(SB), prime2 - // Load slice. - MOVQ b_base+8(FP), p - MOVQ b_len+16(FP), n - LEAQ (p)(n*1), end - SUBQ $32, end - // Load vN from d. MOVQ s+0(FP), d MOVQ 0(d), v1 @@ -192,6 +190,19 @@ TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40 MOVQ 16(d), v3 MOVQ 24(d), v4 + // Handle extra + MOVQ extra+8(FP), p + TESTQ p, p + JZ noExtra + round32() +noExtra: + + // Load slice. + MOVQ b_base+16(FP), p + MOVQ b_len+24(FP), n + LEAQ (p)(n*1), end + SUBQ $32, end + // We don't need to check the loop condition here; this function is // always called with at least one block of data to process. blockLoop() @@ -202,8 +213,4 @@ TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40 MOVQ v3, 16(d) MOVQ v4, 24(d) - // The number of bytes written is p minus the old base pointer. - SUBQ b_base+8(FP), p - MOVQ p, ret+32(FP) - RET diff --git a/xxhash_arm64.s b/xxhash_arm64.s index 7e3145a..f03a3e5 100644 --- a/xxhash_arm64.s +++ b/xxhash_arm64.s @@ -161,8 +161,8 @@ finalize: MOVD h, ret+24(FP) RET -// func writeBlocks(d *Digest, b []byte) int -TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40 +// func writeBlocksArm64(d *Digest, b []byte) +TEXT ·writeBlocksArm64(SB), NOSPLIT|NOFRAME, $0-32 LDP ·primes+0(SB), (prime1, prime2) // Load state. Assume v[1-4] are stored contiguously. @@ -178,6 +178,4 @@ TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40 STP (v1, v2), 0(digest) STP (v3, v4), 16(digest) - BIC $31, n - MOVD n, ret+32(FP) RET diff --git a/xxhash_asm.go b/xxhash_asm.go deleted file mode 100644 index 78f95f2..0000000 --- a/xxhash_asm.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build (amd64 || arm64) && !appengine && gc && !purego -// +build amd64 arm64 -// +build !appengine -// +build gc -// +build !purego - -package xxhash - -// Sum64 computes the 64-bit xxHash digest of b with a zero seed. -// -//go:noescape -func Sum64(b []byte) uint64 - -//go:noescape -func writeBlocks(d *Digest, b []byte) int diff --git a/xxhash_asm_amd64.go b/xxhash_asm_amd64.go new file mode 100644 index 0000000..1ff7c47 --- /dev/null +++ b/xxhash_asm_amd64.go @@ -0,0 +1,31 @@ +//go:build amd64 && !appengine && gc && !purego && go1.22 +// +build amd64,!appengine,gc,!purego,go1.22 + +package xxhash + +import "github.com/klauspost/cpuid/v2" + +var useAvx512 = cpuid.CPU.Supports( + cpuid.AVX, + cpuid.AVX2, + cpuid.AVX512DQ, + cpuid.AVX512F, + cpuid.AVX512VL, + cpuid.BMI1, + +// Today, vectorized 64 bits integer multiples positively sucks on intel, +// with ILP a single scalar unit is multiple times faster. +// This means sometime we wont be using the AVX512 when under virtualization +// because vendor will be the hypervisor, but in my experience that is rare. +// Most virtualization setups defaults to reporting the vendorid of the host. +) && cpuid.CPU.IsVendor(cpuid.AMD) + +// Sum64 computes the 64-bit xxHash digest of b with a zero seed. +// +//go:noescape +func Sum64(b []byte) uint64 + +// extra is a first block before b, it may be nil then skip it. +// +//go:noescape +func writeBlocks(d *Digest, extra *[32]byte, b []byte) diff --git a/xxhash_asm_amd64_old.go b/xxhash_asm_amd64_old.go new file mode 100644 index 0000000..3da4816 --- /dev/null +++ b/xxhash_asm_amd64_old.go @@ -0,0 +1,22 @@ +//go:build amd64 && !appengine && gc && !purego && !go1.22 +// +build amd64,!appengine,gc,!purego,!go1.22 + +// The avx512 impl relies on PCALIGN. + +package xxhash + +// Sum64 computes the 64-bit xxHash digest of b with a zero seed. +func Sum64(b []byte) uint64 { + return sum64Scalar(b) +} + +//go:noescape +func sum64Scalar(b []byte) uint64 + +// extra is a first block before b, it may be nil then skip it. +func writeBlocks(d *Digest, extra *[32]byte, b []byte) { + return writeBlocksScalar(d, extra, b) +} + +//go:noescape +func writeBlocksScalar(d *Digest, extra *[32]byte, b []byte) diff --git a/xxhash_asm_arm64.go b/xxhash_asm_arm64.go new file mode 100644 index 0000000..0972211 --- /dev/null +++ b/xxhash_asm_arm64.go @@ -0,0 +1,26 @@ +//go:build arm64 && !appengine && gc && !purego +// +build arm64,!appengine,gc,!purego + +package xxhash + +var useAvx512 = false // used in tests + +// Sum64 computes the 64-bit xxHash digest of b with a zero seed. +// +//go:noescape +func Sum64(b []byte) uint64 + +// extra is a first block before b, it may be nil then skip it. +func writeBlocks(d *Digest, extra *[32]byte, b []byte) { + if extra != nil { + // FIXME: handle that logic in ASM, *someone* was lazy and didn't + // cared to learn the arm64 p9 syntax. + // At least this is hopefully on par with how fast the software impl + // it used to be. + writeBlocksArm64(d, extra[:]) + } + writeBlocksArm64(d, b) +} + +//go:noescape +func writeBlocksArm64(d *Digest, b []byte) diff --git a/xxhash_avx512_amd64.s b/xxhash_avx512_amd64.s new file mode 100644 index 0000000..0bbb86b --- /dev/null +++ b/xxhash_avx512_amd64.s @@ -0,0 +1,215 @@ +//go:build !appengine && gc && !purego && go1.22 +// +build !appengine +// +build gc +// +build !purego +// +build go1.22 + +#include "textflag.h" + +DATA ·initWideAvx512<>+0(SB)/8, $0x60ea27eeadc0b5d6 +DATA ·initWideAvx512<>+8(SB)/8, $0xc2b2ae3d27d4eb4f +DATA ·initWideAvx512<>+16(SB)/8, $0x0000000000000000 +DATA ·initWideAvx512<>+24(SB)/8, $0x61c8864e7a143579 +GLOBL ·initWideAvx512<>(SB), NOSPLIT|NOPTR, $32 + +#define p SI +#define h AX +#define end DI +#define temp BX +#define prime1 DX +#define prime2 R8 + +#define state Y0 +#define xstate X0 +#define yprime1 Y1 +#define yprime2 Y2 +#define ytemp Y3 +#define xtemp X3 + +#define yround() \ + VPMULLQ ytemp, yprime2, ytemp \ + VPADDQ ytemp, state, state \ + VPROLQ $31, state, state \ + VPMULLQ state, yprime1, state \ + +#define blockLoop(length) \ + MOVL $0x1f, end \ + ANDNQ length, end, end \ + ADDQ p, end \ + PCALIGN $64 \ +loop_32: \ + VMOVDQU (p), ytemp \ + yround() \ + ADDQ $32, p \ + CMPQ p, end \ + JNE loop_32 + +#define n CX +#define prime3 R9 +#define prime4 R10 +#define prime5 R11 + +// lateMergeRound performs mergeRound on h given the value from round0 +#define lateMergeRound(v) \ + XORQ v, h \ + IMULQ prime1, h \ + ADDQ prime4, h + +// func Sum64(b []byte) uint64 +// Requires: AVX, AVX2, AVX512DQ, AVX512F, AVX512VL, BMI +TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32 + CMPB ·useAvx512(SB), $0x00 + JNE do_avx512 + JMP ·sum64Scalar(SB) +zero: + MOVQ $0xef46db3751d8e999, h + MOVQ h, ret+24(FP) + RET + +do_avx512: + MOVQ b_base+0(FP), p + MOVQ b_len+8(FP), n + MOVQ $0x9e3779b185ebca87, prime1 + MOVQ $0xc2b2ae3d27d4eb4f, prime2 + MOVQ $0x165667b19e3779f9, prime3 + MOVQ $0x85ebca77c2b2ae63, prime4 + MOVQ $0x27d4eb2f165667c5, prime5 + + LEAQ (prime5)(n*1), h // precompute h for the shortcuts + JCXZQ zero + CMPQ n, $3 + JBE loop_1 + CMPQ n, $7 + JBE do_4 + CMPQ n, $31 + JBE loop_8 + + VMOVDQU ·initWideAvx512<>+0(SB), state + VPBROADCASTQ prime1, yprime1 + VPBROADCASTQ prime2, yprime2 + + blockLoop(n) + + VMOVQ xstate, h + ROLQ $1, h + + VPEXTRQ $1, xstate, temp + ROLQ $7, temp + ADDQ temp, h + + VEXTRACTI128 $1, state, xtemp + VMOVQ xtemp, temp + ROLQ $12, temp + ADDQ temp, h + + VPEXTRQ $1, xtemp, temp + ROLQ $18, temp + ADDQ temp, h + + // round0 for mergeRound + VPMULLQ yprime2, state, state + VPROLQ $0x1f, state, state + VPMULLQ yprime1, state, state + + VMOVQ xstate, temp + lateMergeRound(temp) + + VPEXTRQ $1, xstate, temp + lateMergeRound(temp) + + VEXTRACTI128 $1, state, xtemp + VMOVQ xtemp, temp + lateMergeRound(temp) + + VPEXTRQ $1, xtemp, temp + VZEROUPPER + lateMergeRound(temp) + + ADDQ n, h + ANDQ $0x1f, n + + CMPQ n, $8 + JB skip_8 +loop_8: + MOVQ (p), temp + ADDQ $8, p + SUBQ $8, n + IMULQ prime2, temp + ROLQ $31, temp + IMULQ prime1, temp + XORQ temp, h + ROLQ $27, h + IMULQ prime1, h + ADDQ prime4, h + CMPQ n, $8 + JAE loop_8 +skip_8: + + CMPQ n, $4 + JB skip_4 +do_4: + MOVL (p), temp + ADDQ $4, p + SUBQ $4, n + IMULQ prime1, temp + XORQ temp, h + ROLQ $23, h + IMULQ prime2, h + ADDQ prime3, h +skip_4: + + JCXZQ skip_1 +loop_1: + MOVBLZX (p), temp + INCQ p + IMULQ prime5, temp + XORQ temp, h + ROLQ $0x0b, h + IMULQ prime1, h + DECQ n + JNZ loop_1 // could be a LOOP but go tool asm wont assemble it :'( +skip_1: + + MOVQ h, temp + SHRQ $33, temp + XORQ temp, h + IMULQ prime2, h + MOVQ h, temp + SHRQ $29, temp + XORQ temp, h + IMULQ prime3, h + MOVQ h, temp + SHRQ $32, temp + XORQ temp, h + MOVQ h, ret+24(FP) + RET + +#define extrap CX +#define l R9 + +// func writeBlocksAvx512(d *[4]uint64, extra *[32]byte, b []byte) +// Requires: AVX, AVX2, AVX512DQ, AVX512F, AVX512VL, BMI +TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40 + CMPB ·useAvx512(SB), $0x00 + JNE do_avx512 + JMP ·writeBlocksScalar(SB) + +do_avx512: + MOVQ d+0(FP), h + MOVQ extra+8(FP), extrap + MOVQ b_base+16(FP), p + MOVQ b_len+24(FP), l + VMOVDQU (h), state + MOVQ $0x9e3779b185ebca87, prime1 + VPBROADCASTQ prime1, yprime1 + MOVQ $0xc2b2ae3d27d4eb4f, prime2 + VPBROADCASTQ prime2, yprime2 + JCXZQ skip_extra + VMOVDQU (extrap), ytemp + yround() + +skip_extra: + blockLoop(l) + VMOVDQU state, (h) + VZEROUPPER + RET diff --git a/xxhash_other.go b/xxhash_other.go index 118e49e..67395cd 100644 --- a/xxhash_other.go +++ b/xxhash_other.go @@ -3,6 +3,8 @@ package xxhash +var useAvx512 = false + // Sum64 computes the 64-bit xxHash digest of b with a zero seed. func Sum64(b []byte) uint64 { // A simpler version would be @@ -61,16 +63,26 @@ func Sum64(b []byte) uint64 { return h } -func writeBlocks(d *Digest, b []byte) int { - v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4 - n := len(b) - for len(b) >= 32 { - v1 = round(v1, u64(b[0:8:len(b)])) - v2 = round(v2, u64(b[8:16:len(b)])) - v3 = round(v3, u64(b[16:24:len(b)])) - v4 = round(v4, u64(b[24:32:len(b)])) - b = b[32:len(b):len(b)] +func writeBlocks(d *Digest, extra *[32]byte, b []byte) { + v1, v2, v3, v4 := d.s[0], d.s[1], d.s[2], d.s[3] + var s []byte + if extra != nil { + s = extra[:] + } else { + s = b + } +reHash: + for len(s) >= 32 { + v1 = round(v1, u64(s[0:8:len(s)])) + v2 = round(v2, u64(s[8:16:len(s)])) + v3 = round(v3, u64(s[16:24:len(s)])) + v4 = round(v4, u64(s[24:32:len(s)])) + s = s[32:len(s):len(s)] + } + if extra != nil { + s = b + extra = nil + goto reHash } - d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4 - return n - len(b) + d.s[0], d.s[1], d.s[2], d.s[3] = v1, v2, v3, v4 } diff --git a/xxhash_test.go b/xxhash_test.go index 8e2f456..5ecae8f 100644 --- a/xxhash_test.go +++ b/xxhash_test.go @@ -9,7 +9,28 @@ import ( "testing" ) -func TestAll(t *testing.T) { +func Test(t *testing.T) { + for { + var suffix string + if useAvx512 { + suffix = "-avx512" + } + + t.Run("All"+suffix, testAll) + t.Run("Reset"+suffix, testReset) + t.Run("ResetWithSeed"+suffix, testResetWithSeed) + t.Run("BinaryMarshaling"+suffix, testBinaryMarshaling) + + if useAvx512 { + useAvx512 = false + defer func() { useAvx512 = true }() + continue + } + return + } +} + +func testAll(t *testing.T) { // Exactly 63 characters, which exercises all code paths. const s63 = "Call me Ishmael. Some years ago--never mind how long precisely-" for _, tt := range []struct { @@ -94,7 +115,7 @@ func testSum(t *testing.T, input string, want uint64) { } } -func TestReset(t *testing.T) { +func testReset(t *testing.T) { parts := []string{"The quic", "k br", "o", "wn fox jumps", " ov", "er the lazy ", "dog."} d := New() for _, part := range parts { @@ -111,7 +132,7 @@ func TestReset(t *testing.T) { } } -func TestResetWithSeed(t *testing.T) { +func testResetWithSeed(t *testing.T) { parts := []string{"The quic", "k br", "o", "wn fox jumps", " ov", "er the lazy ", "dog."} d := NewWithSeed(123) for _, part := range parts { @@ -128,7 +149,7 @@ func TestResetWithSeed(t *testing.T) { } } -func TestBinaryMarshaling(t *testing.T) { +func testBinaryMarshaling(t *testing.T) { d := New() d.WriteString("abc") b, err := d.MarshalBinary() @@ -173,7 +194,7 @@ func TestAllocs(t *testing.T) { // intermediate []byte ought not to escape. // (See https://github.com/cespare/xxhash/pull/2.) t.Run("Sum64", func(t *testing.T) { - testAllocs(t, func() { + runAllocs(t, func() { sink = Sum64([]byte(shortStr)) }) }) @@ -182,7 +203,7 @@ func TestAllocs(t *testing.T) { // hash.Hash64 which forces an allocation.) t.Run("Digest", func(t *testing.T) { b := []byte("asdf") - testAllocs(t, func() { + runAllocs(t, func() { d := New() d.Write(b) sink = d.Sum64() @@ -190,7 +211,7 @@ func TestAllocs(t *testing.T) { }) } -func testAllocs(t *testing.T, fn func()) { +func runAllocs(t *testing.T, fn func()) { t.Helper() if allocs := int(testing.AllocsPerRun(10, fn)); allocs > 0 { t.Fatalf("got %d allocation(s) (want zero)", allocs) diff --git a/xxhash_unsafe_test.go b/xxhash_unsafe_test.go index 6d6f93c..9f5f76c 100644 --- a/xxhash_unsafe_test.go +++ b/xxhash_unsafe_test.go @@ -13,12 +13,12 @@ import ( func TestStringAllocs(t *testing.T) { longStr := strings.Repeat("a", 1000) t.Run("Sum64String", func(t *testing.T) { - testAllocs(t, func() { + runAllocs(t, func() { sink = Sum64String(longStr) }) }) t.Run("Digest.WriteString", func(t *testing.T) { - testAllocs(t, func() { + runAllocs(t, func() { d := New() d.WriteString(longStr) sink = d.Sum64()