Permalink
Browse files

strconv: optimize Atoi for common case

Benchmark results on GOOS=linux:

GOARCH=amd64

name              old time/op  new time/op  delta
Atoi/Pos/7bit-4   20.1ns ± 2%   8.6ns ± 1%  -57.34%  (p=0.000 n=10+10)
Atoi/Pos/26bit-4  25.8ns ± 7%  11.9ns ± 0%  -53.91%  (p=0.000 n=10+8)
Atoi/Pos/31bit-4  27.3ns ± 2%  13.2ns ± 1%  -51.56%  (p=0.000 n=10+10)
Atoi/Pos/56bit-4  37.2ns ± 5%  18.2ns ± 1%  -51.26%  (p=0.000 n=10+10)
Atoi/Pos/63bit-4  38.7ns ± 1%  38.6ns ± 1%     ~     (p=0.297 n=9+10)
Atoi/Neg/7bit-4   17.6ns ± 1%   7.2ns ± 0%  -59.22%  (p=0.000 n=10+10)
Atoi/Neg/26bit-4  24.4ns ± 1%  12.4ns ± 1%  -49.28%  (p=0.000 n=10+10)
Atoi/Neg/31bit-4  26.9ns ± 0%  14.0ns ± 1%  -47.88%  (p=0.000 n=7+10)
Atoi/Neg/56bit-4  36.2ns ± 1%  19.5ns ± 0%  -46.24%  (p=0.000 n=10+9)
Atoi/Neg/63bit-4  38.9ns ± 1%  38.8ns ± 1%     ~     (p=0.385 n=9+10)

GOARCH=386

name              old time/op  new time/op  delta
Atoi/Pos/7bit-4   89.6ns ± 1%   8.2ns ± 1%  -90.84%  (p=0.000 n=9+10)
Atoi/Pos/26bit-4   187ns ± 2%    12ns ± 1%  -93.71%  (p=0.000 n=10+9)
Atoi/Pos/31bit-4   225ns ± 1%   225ns ± 1%     ~     (p=0.995 n=10+10)
Atoi/Neg/7bit-4   86.2ns ± 1%   8.5ns ± 1%  -90.14%  (p=0.000 n=10+10)
Atoi/Neg/26bit-4   183ns ± 1%    13ns ± 1%  -92.77%  (p=0.000 n=9+10)
Atoi/Neg/31bit-4   223ns ± 0%   223ns ± 0%     ~     (p=0.247 n=8+9)

Fixes #20557

Change-Id: Ib6245d88cffd4b037419e2bf8e4a71b86c6d773f
Reviewed-on: https://go-review.googlesource.com/44692
Reviewed-by: Robert Griesemer <gri@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
  • Loading branch information...
valyala authored and griesemer committed Jun 2, 2017
1 parent 180bfc4 commit 46aa9f5437b000fad3696b0cd9fd97995da16411
Showing with 115 additions and 14 deletions.
  1. +28 −0 src/strconv/atoi.go
  2. +87 −14 src/strconv/atoi_test.go
View
@@ -201,6 +201,34 @@ func ParseInt(s string, base int, bitSize int) (i int64, err error) {
// Atoi returns the result of ParseInt(s, 10, 0) converted to type int.
func Atoi(s string) (int, error) {
const fnAtoi = "Atoi"
sLen := len(s)
if intSize == 32 && (0 < sLen && sLen < 10) ||
intSize == 64 && (0 < sLen && sLen < 19) {
// Fast path for small integers that fit int type.
s0 := s
if s[0] == '-' || s[0] == '+' {
s = s[1:]
if len(s) < 1 {
return 0, &NumError{fnAtoi, s0, ErrSyntax}
}
}
n := 0
for _, ch := range []byte(s) {
ch -= '0'
if ch > 9 {
return 0, &NumError{fnAtoi, s0, ErrSyntax}
}
n = n*10 + int(ch)
}
if s0[0] == '-' {
n = -n
}
return n, nil
}
// Slow path for invalid or big integers.
i64, err := ParseInt(s, 10, 0)
if nerr, ok := err.(*NumError); ok {
nerr.Func = fnAtoi
View
@@ -6,6 +6,7 @@ package strconv_test
import (
"errors"
"fmt"
"reflect"
. "strconv"
"testing"
@@ -354,6 +355,37 @@ func TestParseInt(t *testing.T) {
}
}
func TestAtoi(t *testing.T) {
switch IntSize {
case 32:
for i := range parseInt32Tests {
test := &parseInt32Tests[i]
out, err := Atoi(test.in)
var testErr error
if test.err != nil {
testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err}
}
if int(test.out) != out || !reflect.DeepEqual(testErr, err) {
t.Errorf("Atoi(%q) = %v, %v want %v, %v",
test.in, out, err, test.out, testErr)
}
}
case 64:
for i := range parseInt64Tests {
test := &parseInt64Tests[i]
out, err := Atoi(test.in)
var testErr error
if test.err != nil {
testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err}
}
if test.out != int64(out) || !reflect.DeepEqual(testErr, err) {
t.Errorf("Atoi(%q) = %v, %v want %v, %v",
test.in, out, err, test.out, testErr)
}
}
}
}
func bitSizeErrStub(name string, bitSize int) error {
return BitSizeError(name, "0", bitSize)
}
@@ -448,26 +480,67 @@ func TestNumError(t *testing.T) {
}
}
func BenchmarkAtoi(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseInt("12345678", 10, 0)
}
func BenchmarkParseInt(b *testing.B) {
b.Run("Pos", func(b *testing.B) {
benchmarkParseInt(b, 1)
})
b.Run("Neg", func(b *testing.B) {
benchmarkParseInt(b, -1)
})
}
func BenchmarkAtoiNeg(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseInt("-12345678", 10, 0)
}
type benchCase struct {
name string
num int64
}
func BenchmarkAtoi64(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseInt("12345678901234", 10, 64)
func benchmarkParseInt(b *testing.B, neg int) {
cases := []benchCase{
{"7bit", 1<<7 - 1},
{"26bit", 1<<26 - 1},
{"31bit", 1<<31 - 1},
{"56bit", 1<<56 - 1},
{"63bit", 1<<63 - 1},
}
for _, cs := range cases {
b.Run(cs.name, func(b *testing.B) {
s := fmt.Sprintf("%d", cs.num*int64(neg))
for i := 0; i < b.N; i++ {
out, _ := ParseInt(s, 10, 64)
BenchSink += int(out)
}
})
}
}
func BenchmarkAtoi64Neg(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseInt("-12345678901234", 10, 64)
func BenchmarkAtoi(b *testing.B) {
b.Run("Pos", func(b *testing.B) {
benchmarkAtoi(b, 1)
})
b.Run("Neg", func(b *testing.B) {
benchmarkAtoi(b, -1)
})
}
func benchmarkAtoi(b *testing.B, neg int) {
cases := []benchCase{
{"7bit", 1<<7 - 1},
{"26bit", 1<<26 - 1},
{"31bit", 1<<31 - 1},
}
if IntSize == 64 {
cases = append(cases, []benchCase{
{"56bit", 1<<56 - 1},
{"63bit", 1<<63 - 1},
}...)
}
for _, cs := range cases {
b.Run(cs.name, func(b *testing.B) {
s := fmt.Sprintf("%d", cs.num*int64(neg))
for i := 0; i < b.N; i++ {
out, _ := Atoi(s)
BenchSink += out
}
})
}
}

0 comments on commit 46aa9f5

Please sign in to comment.