mirror of
https://github.com/golang/go
synced 2024-11-19 17:44: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.
|
// Atoi returns the result of ParseInt(s, 10, 0) converted to type int.
|
||||||
func Atoi(s string) (int, error) {
|
func Atoi(s string) (int, error) {
|
||||||
const fnAtoi = "Atoi"
|
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)
|
i64, err := ParseInt(s, 10, 0)
|
||||||
if nerr, ok := err.(*NumError); ok {
|
if nerr, ok := err.(*NumError); ok {
|
||||||
nerr.Func = fnAtoi
|
nerr.Func = fnAtoi
|
||||||
|
@ -6,6 +6,7 @@ package strconv_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
. "strconv"
|
. "strconv"
|
||||||
"testing"
|
"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 {
|
func bitSizeErrStub(name string, bitSize int) error {
|
||||||
return BitSizeError(name, "0", bitSize)
|
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) {
|
func BenchmarkAtoi(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
b.Run("Pos", func(b *testing.B) {
|
||||||
ParseInt("12345678", 10, 0)
|
benchmarkAtoi(b, 1)
|
||||||
}
|
})
|
||||||
|
b.Run("Neg", func(b *testing.B) {
|
||||||
|
benchmarkAtoi(b, -1)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAtoiNeg(b *testing.B) {
|
func benchmarkAtoi(b *testing.B, neg int) {
|
||||||
for i := 0; i < b.N; i++ {
|
cases := []benchCase{
|
||||||
ParseInt("-12345678", 10, 0)
|
{"7bit", 1<<7 - 1},
|
||||||
}
|
{"26bit", 1<<26 - 1},
|
||||||
}
|
{"31bit", 1<<31 - 1},
|
||||||
|
}
|
||||||
func BenchmarkAtoi64(b *testing.B) {
|
if IntSize == 64 {
|
||||||
for i := 0; i < b.N; i++ {
|
cases = append(cases, []benchCase{
|
||||||
ParseInt("12345678901234", 10, 64)
|
{"56bit", 1<<56 - 1},
|
||||||
}
|
{"63bit", 1<<63 - 1},
|
||||||
}
|
}...)
|
||||||
|
}
|
||||||
func BenchmarkAtoi64Neg(b *testing.B) {
|
for _, cs := range cases {
|
||||||
for i := 0; i < b.N; i++ {
|
b.Run(cs.name, func(b *testing.B) {
|
||||||
ParseInt("-12345678901234", 10, 64)
|
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