strconv¶
A drop-in fast path for base-10 integer parsing whose results — both the
value and the error — are byte-identical to
strconv. The hot case (a clean run of ASCII
decimal digits, base 10) is crunched by a SIMD kernel generated by
go-asmgen (amd64 SSE, ppc64le VSX,
s390x vector facility); everything ambiguous is delegated verbatim to
strconv. Pure Go, CGO_ENABLED=0, stable Go.
Repository →
API¶
v, err := strconv.ParseUint(s, 10, 64) // same value + error as strconv.ParseUint
n, err := strconv.Atoi(s) // same value + error as strconv.Atoi
i, err := strconv.ParseInt(s, 10, 64) // same value + error as strconv.ParseInt
| op | amd64 | ppc64le | s390x | arm64 / loong64 / riscv64 |
|---|---|---|---|---|
| ParseUint / ParseInt / Atoi (base 10) | SSE2/SSSE3 kernel | VSX kernel | vector facility kernel (big-endian) | scalar — NEON/LSX/RVV planned |
The decimal fold is implemented for six 64-bit targets: the amd64, ppc64le
and s390x kernels are SIMD; arm64, loong64 and riscv64 use the scalar fold
(NEON/LSX/RVV ports planned). The VSX and z/Architecture kernels mirror the amd64
staged multiply-accumulate. On big-endian s390x, the digits are loaded with
VL (element 0 = most significant digit) and the power-of-ten weights are laid
out in big-endian lane order — the result is bit-identical to strconv. ppc64le
and s390x are qemu-validated for correctness (value and full error string
matched against strconv under qemu-user); native perf is pending.
Correctness first¶
A correct-but-modest result beats a fast-but-wrong one. The SIMD fast path is
taken only for the unambiguous case and only ever returns a nil error:
base 10, bitSize 0 or 64, an optional leading sign (scalar), then a pure run of
non-overflowing decimal digits long enough to be worth the call. Anything
else — other bases, prefixes (0x/0o/0b), _ separators, empty input, a
non-digit byte, overflow, over-long input, unusual bitSize — falls back to the
real strconv, so the value and the full *strconv.NumError match exactly. This
is proven by FuzzParseUint/FuzzAtoi/FuzzParseInt, comparing value and
the full error string against strconv, plus an exhaustive table.
Algorithm¶
The Lemire "parse a number at a gigabyte per second" SIMD decimal core, on whole 8-digit groups (amd64 SSE):
- Load 8 chars into XMM (
MOVQ). - Validate —
d = c-'0'(PSUBB); valid iff0 ≤ d ≤ 9, via twoPCMPGTB. Any out-of-range lane makesPMOVMSKBnon-zero and the group is rejected (the wrapper re-walks it scalarly so a non-digit ends the run exactly asstrconv). - Fuse —
PMADDUBSW [10,1,…]→ four 2-digit words;PMULLW [100,1,…]+PHADDW→ two 4-digit words; the finalhi·10000+lois combined in Go.
A missing mnemonic
The canonical Lemire kernel fuses with one PMADDWD (SSE2,
[100,1,100,1]), but Go's assembler does not recognise the SSE2
PMADDWD form — only VEX-encoded VPMADDWD. This package uses PMULLW+
PHADDW (both present); identical result. Worth an upstream cmd/asm patch.
Performance¶
Native amd64, go1.26.4, GOAMD64=v3, median, inputs where the SIMD path is
taken:
| call | SIMD | strconv |
speedup |
|---|---|---|---|
ParseUint 16 digits |
156 ns | 176 ns | 1.13× |
ParseUint 19 digits |
172 ns | 197 ns | 1.15× |
Atoi 19 digits |
177 ns | 243 ns | 1.37× |
ParseUint/Atoi ≤ 8 digits |
delegated → strconv |
— | 1.00× |
Honest findings:
- The win is real but narrow, and only on long inputs. Go's scalar parsers
are already very tight; for short strings the non-inlinable assembly CALL plus
the per-group
×1e8swamps the SIMD digit-crunch. Atoiis special — it has an inlined fast path for inputs < 19 digits that SIMD cannot beat. SoAtoitakes the SIMD path only at 19 digits;ParseUint/ParseInttake it from 16 digits.- So SIMD is taken only where it measurably wins; every shorter input
delegates to
strconv. The package therefore never regresses. GOAMD64matters — underv1the kernel is roughly par; underv3(the realistic modern-x86 default) it is the 1.1–1.4× above.
A correct drop-in (fuzz-proven value + error equality) with a modest, honest SIMD win (1.13–1.37×) on long base-10 inputs, and parity everywhere else.
Coverage¶
100% of the Go code (native amd64 + native arm64, plus ppc64le and s390x under
QEMU), exercising the SIMD fast path AND every fallback-to-stdlib branch. The
.s kernels are validated by differential tests against strconv plus fuzzing —
comparing value and the full error string, on native amd64 and under
qemu-user (power9 / s390x) for the VSX and big-endian vector-facility
kernels. BSD-3-Clause.