Skip to content

strconv

CI coverage

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).
  • Validated = c-'0' (PSUBB); valid iff 0 ≤ d ≤ 9, via two PCMPGTB. Any out-of-range lane makes PMOVMSKB non-zero and the group is rejected (the wrapper re-walks it scalarly so a non-digit ends the run exactly as strconv).
  • FusePMADDUBSW [10,1,…] → four 2-digit words; PMULLW [100,1,…]+ PHADDW → two 4-digit words; the final hi·10000+lo is 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 ×1e8 swamps the SIMD digit-crunch.
  • Atoi is special — it has an inlined fast path for inputs < 19 digits that SIMD cannot beat. So Atoi takes the SIMD path only at 19 digits; ParseUint/ParseInt take it from 16 digits.
  • So SIMD is taken only where it measurably wins; every shorter input delegates to strconv. The package therefore never regresses.
  • GOAMD64 matters — under v1 the kernel is roughly par; under v3 (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.