Commit 3d48ae35 authored by Sam Arnold's avatar Sam Arnold Committed by Robert Griesemer

strconv: Speed improvement to number parsing

Run underscore validation only if we have seen underscores.

Some performance results on my laptop:
name                   old time/op  new time/op  delta
Atof64Decimal-12       30.5ns ± 0%  23.8ns ± 0%  -22.02%  (p=0.016 n=5+4)
Atof64Float-12         39.0ns ± 0%  28.7ns ± 0%  -26.39%  (p=0.002 n=6+6)
Atof64FloatExp-12      64.4ns ± 1%  54.4ns ± 1%  -15.65%  (p=0.002 n=6+6)
Atof64Big-12            115ns ± 1%    87ns ± 1%  -24.45%  (p=0.002 n=6+6)
Atof64RandomBits-12     187ns ±14%   156ns ±19%  -16.46%  (p=0.032 n=6+6)
Atof64RandomFloats-12   126ns ± 0%   105ns ± 1%  -16.65%  (p=0.000 n=6+5)
Atof32Decimal-12       32.0ns ± 1%  24.0ns ± 1%  -24.97%  (p=0.002 n=6+6)
Atof32Float-12         37.1ns ± 1%  27.0ns ± 1%  -27.42%  (p=0.002 n=6+6)
Atof32FloatExp-12      68.4ns ± 1%  54.2ns ± 1%  -20.77%  (p=0.002 n=6+6)
Atof32Random-12        92.0ns ± 1%  77.4ns ± 0%  -15.81%  (p=0.000 n=6+5)
ParseInt/Pos/7bit-12   19.4ns ± 1%  13.8ns ±10%  -28.94%  (p=0.002 n=6+6)
ParseInt/Pos/26bit-12  29.1ns ± 1%  19.8ns ± 2%  -31.92%  (p=0.002 n=6+6)
ParseInt/Pos/31bit-12  33.1ns ± 0%  22.3ns ± 3%  -32.62%  (p=0.004 n=5+6)
ParseInt/Pos/56bit-12  47.8ns ± 1%  30.7ns ± 1%  -35.78%  (p=0.004 n=6+5)
ParseInt/Pos/63bit-12  51.9ns ± 1%  33.4ns ± 2%  -35.49%  (p=0.002 n=6+6)
ParseInt/Neg/7bit-12   18.5ns ± 4%  13.4ns ± 3%  -27.88%  (p=0.002 n=6+6)
ParseInt/Neg/26bit-12  28.4ns ± 3%  19.7ns ± 3%  -30.38%  (p=0.002 n=6+6)
ParseInt/Neg/31bit-12  31.9ns ± 1%  21.8ns ± 2%  -31.56%  (p=0.002 n=6+6)
ParseInt/Neg/56bit-12  46.2ns ± 0%  30.6ns ± 1%  -33.73%  (p=0.004 n=5+6)
ParseInt/Neg/63bit-12  50.2ns ± 1%  33.2ns ± 1%  -33.96%  (p=0.002 n=6+6)

Fixes #33330

Change-Id: I119da66457c2fbaf6e88bb90cf56417a16df8f0e
Reviewed-on: https://go-review.googlesource.com/c/go/+/187957
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRobert Griesemer <gri@golang.org>
parent 22355d6c
...@@ -84,7 +84,7 @@ func (b *decimal) set(s string) (ok bool) { ...@@ -84,7 +84,7 @@ func (b *decimal) set(s string) (ok bool) {
for ; i < len(s); i++ { for ; i < len(s); i++ {
switch { switch {
case s[i] == '_': case s[i] == '_':
// underscoreOK already called // readFloat already checked underscores
continue continue
case s[i] == '.': case s[i] == '.':
if sawdot { if sawdot {
...@@ -140,7 +140,7 @@ func (b *decimal) set(s string) (ok bool) { ...@@ -140,7 +140,7 @@ func (b *decimal) set(s string) (ok bool) {
e := 0 e := 0
for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ { for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ {
if s[i] == '_' { if s[i] == '_' {
// underscoreOK already called // readFloat already checked underscores
continue continue
} }
if e < 10000 { if e < 10000 {
...@@ -159,10 +159,11 @@ func (b *decimal) set(s string) (ok bool) { ...@@ -159,10 +159,11 @@ func (b *decimal) set(s string) (ok bool) {
} }
// readFloat reads a decimal mantissa and exponent from a float // readFloat reads a decimal mantissa and exponent from a float
// string representation. It returns ok==false if the number could // string representation. It returns ok==false if the number
// not fit return types or is invalid. // is invalid.
func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) { func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
i := 0 i := 0
underscores := false
// optional sign // optional sign
if i >= len(s) { if i >= len(s) {
...@@ -195,7 +196,7 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) { ...@@ -195,7 +196,7 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
for ; i < len(s); i++ { for ; i < len(s); i++ {
switch c := s[i]; true { switch c := s[i]; true {
case c == '_': case c == '_':
// underscoreOK already called underscores = true
continue continue
case c == '.': case c == '.':
...@@ -271,7 +272,7 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) { ...@@ -271,7 +272,7 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
e := 0 e := 0
for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ { for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ {
if s[i] == '_' { if s[i] == '_' {
// underscoreOK already called underscores = true
continue continue
} }
if e < 10000 { if e < 10000 {
...@@ -291,6 +292,11 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) { ...@@ -291,6 +292,11 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
if mantissa != 0 { if mantissa != 0 {
exp = dp - ndMant exp = dp - ndMant
} }
if underscores && !underscoreOK(s) {
return
}
ok = true ok = true
return return
} }
...@@ -554,12 +560,16 @@ func atof32(s string) (f float32, err error) { ...@@ -554,12 +560,16 @@ func atof32(s string) (f float32, err error) {
} }
mantissa, exp, neg, trunc, hex, ok := readFloat(s) mantissa, exp, neg, trunc, hex, ok := readFloat(s)
if hex && ok { if !ok {
return 0, syntaxError(fnParseFloat, s)
}
if hex {
f, err := atofHex(s, &float32info, mantissa, exp, neg, trunc) f, err := atofHex(s, &float32info, mantissa, exp, neg, trunc)
return float32(f), err return float32(f), err
} }
if optimize && ok { if optimize {
// Try pure floating-point arithmetic conversion. // Try pure floating-point arithmetic conversion.
if !trunc { if !trunc {
if f, ok := atof32exact(mantissa, exp, neg); ok { if f, ok := atof32exact(mantissa, exp, neg); ok {
...@@ -597,11 +607,15 @@ func atof64(s string) (f float64, err error) { ...@@ -597,11 +607,15 @@ func atof64(s string) (f float64, err error) {
} }
mantissa, exp, neg, trunc, hex, ok := readFloat(s) mantissa, exp, neg, trunc, hex, ok := readFloat(s)
if hex && ok { if !ok {
return 0, syntaxError(fnParseFloat, s)
}
if hex {
return atofHex(s, &float64info, mantissa, exp, neg, trunc) return atofHex(s, &float64info, mantissa, exp, neg, trunc)
} }
if optimize && ok { if optimize {
// Try pure floating-point arithmetic conversion. // Try pure floating-point arithmetic conversion.
if !trunc { if !trunc {
if f, ok := atof64exact(mantissa, exp, neg); ok { if f, ok := atof64exact(mantissa, exp, neg); ok {
...@@ -658,9 +672,6 @@ func atof64(s string) (f float64, err error) { ...@@ -658,9 +672,6 @@ func atof64(s string) (f float64, err error) {
// ParseFloat recognizes the strings "NaN", "+Inf", and "-Inf" as their // ParseFloat recognizes the strings "NaN", "+Inf", and "-Inf" as their
// respective special floating point values. It ignores case when matching. // respective special floating point values. It ignores case when matching.
func ParseFloat(s string, bitSize int) (float64, error) { func ParseFloat(s string, bitSize int) (float64, error) {
if !underscoreOK(s) {
return 0, syntaxError(fnParseFloat, s)
}
if bitSize == 32 { if bitSize == 32 {
f, err := atof32(s) f, err := atof32(s)
return float64(f), err return float64(f), err
......
...@@ -58,7 +58,7 @@ const maxUint64 = 1<<64 - 1 ...@@ -58,7 +58,7 @@ const maxUint64 = 1<<64 - 1
func ParseUint(s string, base int, bitSize int) (uint64, error) { func ParseUint(s string, base int, bitSize int) (uint64, error) {
const fnParseUint = "ParseUint" const fnParseUint = "ParseUint"
if s == "" || !underscoreOK(s) { if s == "" {
return 0, syntaxError(fnParseUint, s) return 0, syntaxError(fnParseUint, s)
} }
...@@ -113,12 +113,13 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) { ...@@ -113,12 +113,13 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) {
maxVal := uint64(1)<<uint(bitSize) - 1 maxVal := uint64(1)<<uint(bitSize) - 1
underscores := false
var n uint64 var n uint64
for _, c := range []byte(s) { for _, c := range []byte(s) {
var d byte var d byte
switch { switch {
case c == '_' && base0: case c == '_' && base0:
// underscoreOK already called underscores = true
continue continue
case '0' <= c && c <= '9': case '0' <= c && c <= '9':
d = c - '0' d = c - '0'
...@@ -146,6 +147,10 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) { ...@@ -146,6 +147,10 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) {
n = n1 n = n1
} }
if underscores && !underscoreOK(s0) {
return 0, syntaxError(fnParseUint, s0)
}
return n, nil return n, nil
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment