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 BenchmarkParseInt(b *testing.B) { + b.Run("Pos", func(b *testing.B) { + benchmarkParseInt(b, 1) + }) + b.Run("Neg", func(b *testing.B) { + benchmarkParseInt(b, -1) + }) +} + +type benchCase struct { + name string + num int64 +} + +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 BenchmarkAtoi(b *testing.B) { - for i := 0; i < b.N; i++ { - ParseInt("12345678", 10, 0) - } + b.Run("Pos", func(b *testing.B) { + benchmarkAtoi(b, 1) + }) + b.Run("Neg", func(b *testing.B) { + benchmarkAtoi(b, -1) + }) } -func BenchmarkAtoiNeg(b *testing.B) { - for i := 0; i < b.N; i++ { - ParseInt("-12345678", 10, 0) - } -} - -func BenchmarkAtoi64(b *testing.B) { - for i := 0; i < b.N; i++ { - ParseInt("12345678901234", 10, 64) - } -} - -func BenchmarkAtoi64Neg(b *testing.B) { - for i := 0; i < b.N; i++ { - ParseInt("-12345678901234", 10, 64) +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 + } + }) } }