]> Cypherpunks repositories - gostls13.git/commitdiff
strconv: Speed improvement to number parsing
authorSam Arnold <sarnold64@bloomberg.net>
Sat, 10 Aug 2019 16:06:50 +0000 (12:06 -0400)
committerRobert Griesemer <gri@golang.org>
Wed, 28 Aug 2019 03:23:59 +0000 (03:23 +0000)
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: Robert Griesemer <gri@golang.org>
src/strconv/atof.go
src/strconv/atoi.go

index 190b25fbf5711732a829931ef9bde7b107228459..23de70b1c9b6cc866e72604393a666b3a8968bdb 100644 (file)
@@ -84,7 +84,7 @@ func (b *decimal) set(s string) (ok bool) {
        for ; i < len(s); i++ {
                switch {
                case s[i] == '_':
-                       // underscoreOK already called
+                       // readFloat already checked underscores
                        continue
                case s[i] == '.':
                        if sawdot {
@@ -140,7 +140,7 @@ func (b *decimal) set(s string) (ok bool) {
                e := 0
                for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ {
                        if s[i] == '_' {
-                               // underscoreOK already called
+                               // readFloat already checked underscores
                                continue
                        }
                        if e < 10000 {
@@ -159,10 +159,11 @@ func (b *decimal) set(s string) (ok bool) {
 }
 
 // readFloat reads a decimal mantissa and exponent from a float
-// string representation. It returns ok==false if the number could
-// not fit return types or is invalid.
+// string representation. It returns ok==false if the number
+// is invalid.
 func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
        i := 0
+       underscores := false
 
        // optional sign
        if i >= len(s) {
@@ -195,7 +196,7 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
        for ; i < len(s); i++ {
                switch c := s[i]; true {
                case c == '_':
-                       // underscoreOK already called
+                       underscores = true
                        continue
 
                case c == '.':
@@ -271,7 +272,7 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
                e := 0
                for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ {
                        if s[i] == '_' {
-                               // underscoreOK already called
+                               underscores = true
                                continue
                        }
                        if e < 10000 {
@@ -291,6 +292,11 @@ func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
        if mantissa != 0 {
                exp = dp - ndMant
        }
+
+       if underscores && !underscoreOK(s) {
+               return
+       }
+
        ok = true
        return
 }
@@ -554,12 +560,16 @@ func atof32(s string) (f float32, err error) {
        }
 
        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)
                return float32(f), err
        }
 
-       if optimize && ok {
+       if optimize {
                // Try pure floating-point arithmetic conversion.
                if !trunc {
                        if f, ok := atof32exact(mantissa, exp, neg); ok {
@@ -597,11 +607,15 @@ func atof64(s string) (f float64, err error) {
        }
 
        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)
        }
 
-       if optimize && ok {
+       if optimize {
                // Try pure floating-point arithmetic conversion.
                if !trunc {
                        if f, ok := atof64exact(mantissa, exp, neg); ok {
@@ -658,9 +672,6 @@ func atof64(s string) (f float64, err error) {
 // ParseFloat recognizes the strings "NaN", "+Inf", and "-Inf" as their
 // respective special floating point values. It ignores case when matching.
 func ParseFloat(s string, bitSize int) (float64, error) {
-       if !underscoreOK(s) {
-               return 0, syntaxError(fnParseFloat, s)
-       }
        if bitSize == 32 {
                f, err := atof32(s)
                return float64(f), err
index e811bc4df1ad2297814d71b9d686c4de2db959f0..131b088e31b5f0522470e0d9b33488f3fd573e98 100644 (file)
@@ -58,7 +58,7 @@ const maxUint64 = 1<<64 - 1
 func ParseUint(s string, base int, bitSize int) (uint64, error) {
        const fnParseUint = "ParseUint"
 
-       if s == "" || !underscoreOK(s) {
+       if s == "" {
                return 0, syntaxError(fnParseUint, s)
        }
 
@@ -113,12 +113,13 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) {
 
        maxVal := uint64(1)<<uint(bitSize) - 1
 
+       underscores := false
        var n uint64
        for _, c := range []byte(s) {
                var d byte
                switch {
                case c == '_' && base0:
-                       // underscoreOK already called
+                       underscores = true
                        continue
                case '0' <= c && c <= '9':
                        d = c - '0'
@@ -146,6 +147,10 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) {
                n = n1
        }
 
+       if underscores && !underscoreOK(s0) {
+               return 0, syntaxError(fnParseUint, s0)
+       }
+
        return n, nil
 }