hex¶
A drop-in fast path for hexadecimal (base16) encoding and decoding, byte-
and error-identical to encoding/hex.
Both directions run a SIMD kernel generated by
go-asmgen; the short tail reuses an
encoding/hex-equivalent scalar loop. Pure Go, CGO_ENABLED=0, stable Go.
Repository →
API¶
s := hex.EncodeToString(data) // same bytes as encoding/hex.EncodeToString
b, err := hex.DecodeString(s) // same bytes + same errors as encoding/hex
Mirrors encoding/hex: Encode, EncodeToString, Decode, DecodeString,
EncodedLen, DecodedLen.
| op | amd64 | ppc64le | s390x | arm64 / loong64 / riscv64 |
|---|---|---|---|---|
| encode | SSE2/SSSE3 + AVX2 (runtime dispatch) | VSX (VPERM table lookup) |
vector facility (VPERM, big-endian) |
scalar (stdlib) — NEON/LSX/RVV planned |
| decode | SSE2/SSSE3 + AVX2 (runtime dispatch) | VSX (VCHLB + VPERM) |
vector facility (VCHLB + VPERM, big-endian) |
scalar (stdlib) — NEON/LSX/RVV planned |
Both directions have SIMD on amd64, ppc64le (VSX) and s390x (vector
facility); the rest fall back to the encoding/hex scalar loop. ppc64le uses
LXVB16X/STXVB16X (natural memory byte order on little-endian, sidestepping
the LXVD2X doubleword swap), minding the VSX↔VMX Vn == VS(32+n) aliasing.
s390x is big-endian: VL puts the first memory byte in the high-order lane.
The ppc64le and s390x kernels are qemu-validated for correctness (the
InvalidByteError offset and ErrLength semantics are byte- and error-identical
there too). s390x is measured on real IBM z15 (VXE2), 2026-07-03, -count=6:
encode ~18×, decode ~3.1× the encoding/hex scalar path. ppc64le native perf
is pending a real POWER system.
Algorithm¶
Encode — per 16 input bytes (32 for AVX2): split each byte into high/low
nibble (PSRLW $4+PAND 0x0f, PAND 0x0f), map each nibble via a PSHUFB
lookup of "0123456789abcdef", interleave the two nibble streams with
PUNPCKLBW/PUNPCKHBW, store. AVX2 doubles the width and recombines lanes with
VPERM2I128.
Decode — per 32 input chars (64 for AVX2): map each ASCII hex char to its
0..15 value while detecting invalid chars in parallel. Validity is three range
checks (['0','9'], ['A','F'], ['a','f']) via PCMPGTB pairs OR'd together.
PMADDUBSW with a {16,1,…} multiplier fuses each (hi,lo) nibble pair into a
byte; PACKUSWB packs the output. If a block contains any invalid char the
kernel stops and a scalar encoding/hex-equivalent loop resumes — producing the
exact InvalidByteError offset and ErrLength. So Decode/DecodeString are
byte- and error-identical to the standard library.
Performance¶
1 MiB random buffer, native amd64 (AMD EPYC, AVX2, GOAMD64=v1, median of 6):
| implementation | kind | encode MB/s | vs stdlib | decode MB/s | vs stdlib |
|---|---|---|---|---|---|
encoding/hex (stdlib) |
scalar | 980 | 1.00× | 2095 | 1.00× |
| this package | SIMD encode and decode | 20023 | 20.4× | 13078 | 6.24× |
tmthrgd/go-hex |
SIMD, archived Sep 2025 | 18785 | 19.2× | 9566 | 4.57× |
On decode this package is 1.37× faster than tmthrgd/go-hex and 6.24× over
stdlib. Forced SSE-only paths are ~16800 MB/s encode and ~6329 MB/s decode.
Go's standard encoding/hex is scalar; see
golang/go#68188, the open proposal
to add SIMD to the standard library.
Coverage¶
100% of the Go code (native amd64 + native arm64, plus QEMU-emulated ppc64le and
s390x jobs). The .s kernels are validated by differential tests against
encoding/hex — including invalid bytes at every offset — plus fuzzing (on real
AVX2 for amd64; under qemu-user for the VSX and big-endian vector-facility
kernels). BSD-3-Clause.