aboutsummaryrefslogtreecommitdiff
path: root/src/strconv
diff options
context:
space:
mode:
authorAliaksandr Valialkin <valyala@gmail.com>2017-06-02 19:16:19 +0300
committerRobert Griesemer <gri@golang.org>2017-08-25 10:17:39 +0000
commit46aa9f5437b000fad3696b0cd9fd97995da16411 (patch)
treecdfcc0e2752f695f085af19c6e418da39889eb50 /src/strconv
parent180bfc4bd49343cb280d1e162a4d326db7b35a6f (diff)
downloadgo-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.go28
-rw-r--r--src/strconv/atoi_test.go101
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
+ }
+ })
}
}