Pure-Go XXH3-64 (the 64-bit variant of the modern XXH3 hash) with the hot
64-byte-stripe accumulator implemented as real SIMD assembly on all six of
Go's 64-bit SIMD architectures — on a plain go build, no cgo, no
GOEXPERIMENT. Digests are bit-exact with the canonical reference
implementation (Cyan4973/xxHash).
import "github.com/go-simd/xxhash"
h := xxhash.Sum64([]byte("hello")) // one-shot
h = xxhash.Sum64String("hello") // string, no copy of the bytes
d := xxhash.New() // streaming hash.Hash64
d.Write([]byte("hel")); d.Write([]byte("lo"))
h = d.Sum64()XXH3 already has an excellent SIMD Go port, zeebo/xxh3, but its assembly covers amd64 and arm64 only (AVX2/SSE2 + NEON). The de-facto XXH64 library, cespare/xxhash, is also amd64 + arm64 only. Neither ships SIMD for ppc64le, s390x, riscv64 or loong64.
This package covers all six. The new value is genuine SIMD XXH3-64 on the four architectures the existing libraries don't touch — including the big-endian s390x — while matching them bit-for-bit everywhere.
The XXH3 long-input path keeps an 8-lane (8×uint64) accumulator and folds the
input 64 bytes at a time: for each lane, dk = data ⊕ secret, then
acc[i] += lo32(dk)·hi32(dk) and acc[i^1] += data. That per-stripe
multiply-add is the SIMD kernel; the scramble, merge and avalanche stay in
portable Go. Assembly is generated by
go-asmgen (*_gen.go, //go:build ignore; the .s is committed):
| arch | ISA | multiply | lane swap |
|---|---|---|---|
| amd64 | SSE2 + AVX2 (auto) | (V)PSHUFD+(V)PMULUDQ |
(V)PSHUFD $0x4e |
| arm64 | NEON | XTN/SHRN/UMULL (WORD-encoded) |
VEXT $8 |
| ppc64le | VSX | VMULOUW on lo/hi (no 64-bit vec mul on POWER8/9) |
VSLDOI $8 |
| s390x | vector facility (big-endian) | VMLOF (odd-word widening) |
VPDI $4 |
| riscv64 | RVV | VMULVV (low 64 = full product) |
VRGATHERVV |
| loong64 | LSX | VMULV (low 64 = full product) |
VSHUF4IW $0x4e |
Because lo32 and hi32 are each < 2³², their product fits in 64 bits, so a
plain low-64 vector multiply is exact — no widening multiply is needed on the
arches that lack one. VSX is baseline on POWER8+ and the vector facility on
z13+, so ppc64le and s390x have no runtime dispatch (the SIMD path is the only
path). amd64 selects AVX2 at run time via golang.org/x/sys/cpu; riscv64 and
loong64 fall back to the portable scalar kernel when the CPU lacks the vector
extension.
XXH3 reads its input little-endian. The Go code always decodes with
encoding/binary.LittleEndian, and the s390x kernel byte-reverses each lane
right after VL (via VPERM with a per-doubleword reversal selector) so the
vector lanes carry true little-endian values. The accumulator math is lane- and
endian-neutral, and the digest is verified bit-exact against the official
vectors and a differential fuzz on s390x under QEMU.
- Official vectors.
TestOfficialVectorschecks every input length 0…4095 against the canonical Cyan4973/xxHash known-answer digests (default secret) — one-shot, string and streaming — on all six architectures. - Anchors.
XXH3_64bits("") = 0x2d06800538d394c2andXXH3_64bits("a") = 0xe6c632b61e964e1f, the values published upstream. - Differential fuzz.
FuzzSum64compares against an independent implementation (zeebo/xxh3) for arbitrary inputs; it has run millions of executions per architecture (including big-endian s390x) with zero mismatches. - 100% statement coverage, enforced in CI on every architecture (native amd64/arm64 + QEMU riscv64/loong64/ppc64le/s390x), with both the SIMD and the scalar-fallback dispatch branches exercised.
Honest numbers. Native arm64 (Apple silicon), Sum64:
| input | go-simd/xxhash | zeebo/xxh3 |
|---|---|---|
| 64 B | ~17.6 GB/s | ~22.8 GB/s |
| 1 KiB | ~13.9 GB/s | ~21.7 GB/s |
| 64 KiB | ~15.2 GB/s | ~22.9 GB/s |
On arm64 zeebo is faster: its NEON kernel folds a whole 1024-byte block per call
with software-pipelined stripes, whereas the arm64 path here still uses a clean
single-stripe kernel (one call per 64 bytes) that prioritises portability and
bit-exactness across six arches over peak throughput. The amd64 path now
folds a whole 1024-byte block per asm call — 16 unrolled stripes plus the
inter-block scramble, accumulator resident in vector registers across the whole
run, prefetched — reaching ~0.94× zeebo at 64 KiB (~50 GB/s, ~5× the original
single-stripe kernel; see BENCHMARKS.md); bringing the same block-at-a-time
structure to the arm64 NEON kernel is the obvious next step.
For ppc64le the SIMD path is QEMU-validated for correctness; native throughput numbers are pending real hardware — but this is an arch the existing libraries provide no SIMD for at all.
For s390x — measured on real z15 (LPAR guest, VXE2, Ubuntu 6.8,
go1.26.4, 2026-07-03): Sum64/65536 = 10333 MB/s vs zeebo 4554 MB/s =
2.27× — BEATS zeebo. This flips the SpacemiT X60 (RVV) story where
zeebo won by ~1.8×; on z15's wider VXE2 execution pipe the VMLOF big-
endian accum path is genuinely faster than zeebo's amd64-tuned scalar.
The same measurement shows z15's Sum64/8192 at 9959 MB/s and
Sum64/1024 at 7951 MB/s. First arch where go-simd/xxhash beats the
strongest pure-Go reference.
amd64 SIMD is correctness- and coverage-validated on a real x86_64 OS; the benchmark figures from that (QEMU-backed) VM are not representative of native AVX2 hardware and are omitted.
BSD-3-Clause.
