Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-simd/xxhash

xxhash — XXH3-64 on all six 64-bit Go SIMD targets

CI coverage

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()

Why this exists

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 SIMD kernel

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.

Big-endian (s390x)

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.

Correctness

  • Official vectors. TestOfficialVectors checks 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("") = 0x2d06800538d394c2 and XXH3_64bits("a") = 0xe6c632b61e964e1f, the values published upstream.
  • Differential fuzz. FuzzSum64 compares 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.

Performance

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.

License

BSD-3-Clause.

About

Pure-Go SIMD xxhash on all 6 of Go's 64-bit SIMD targets

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages