diff options
| author | Aliaksandr Valialkin <valyala@gmail.com> | 2017-06-02 19:16:19 +0300 |
|---|---|---|
| committer | Robert Griesemer <gri@golang.org> | 2017-08-25 10:17:39 +0000 |
| commit | 46aa9f5437b000fad3696b0cd9fd97995da16411 (patch) | |
| tree | cdfcc0e2752f695f085af19c6e418da39889eb50 /src/strconv | |
| parent | 180bfc4bd49343cb280d1e162a4d326db7b35a6f (diff) | |
| download | go-46aa9f5437b000fad3696b0cd9fd97995da16411.tar.xz | |
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>
Diffstat (limited to 'src/strconv')
| -rw-r--r-- | src/strconv/atoi.go | 28 | ||||
| -rw-r--r-- | src/strconv/atoi_test.go | 101 |
2 files changed, 115 insertions, 14 deletions
diff --git a/src/strconv/atoi.go b/src/strconv/atoi.go index e1ac42716c..bebed04820 100644 --- a/src/strconv/atoi.go +++ b/src/strconv/atoi.go @@ -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 diff --git a/src/strconv/atoi_test.go b/src/strconv/atoi_test.go index 94844c7e10..e2f505a665 100644 --- a/src/strconv/atoi_test.go +++ b/src/strconv/atoi_test.go @@ -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 + } + }) } } |
