mirror of
https://github.com/golang/go
synced 2024-11-19 14:54:43 -07:00
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>
This commit is contained in:
parent
180bfc4bd4
commit
46aa9f5437
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user