mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-14 15:56:44 +03:00
Improve performance of ParseUfloat (#1865)
* Improve performance of ParseUfloat function
Replaced `offset` handling logic with more efficient math.Pow10 based calculation.
goos: linux
goarch: amd64
pkg: github.com/valyala/fasthttp
cpu: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ParseUfloat-8 44.22n ± 0% 31.06n ± 0% -29.76% (n=50)
* fix: lint error return value is not checked
* Handling uint64 overflow issues
* Implement ParseUfloat by calling strconv.ParseFloat
* fix: lint error
This commit is contained in:
+9
-52
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -172,61 +171,19 @@ func parseUintBuf(b []byte) (int, int, error) {
|
||||
return v, n, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errEmptyFloat = errors.New("empty float number")
|
||||
errDuplicateFloatPoint = errors.New("duplicate point found in float number")
|
||||
errUnexpectedFloatEnd = errors.New("unexpected end of float number")
|
||||
errInvalidFloatExponent = errors.New("invalid float number exponent")
|
||||
errUnexpectedFloatChar = errors.New("unexpected char found in float number")
|
||||
)
|
||||
|
||||
// ParseUfloat parses unsigned float from buf.
|
||||
func ParseUfloat(buf []byte) (float64, error) {
|
||||
if len(buf) == 0 {
|
||||
return -1, errEmptyFloat
|
||||
// The implementation of parsing a float string is not easy.
|
||||
// We believe that the conservative approach is to call strconv.ParseFloat.
|
||||
// https://github.com/valyala/fasthttp/pull/1865
|
||||
res, err := strconv.ParseFloat(b2s(buf), 64)
|
||||
if res < 0 {
|
||||
return -1, errors.New("negative input is invalid")
|
||||
}
|
||||
b := buf
|
||||
var v uint64
|
||||
offset := 1.0
|
||||
var pointFound bool
|
||||
for i, c := range b {
|
||||
if c < '0' || c > '9' {
|
||||
if c == '.' {
|
||||
if pointFound {
|
||||
return -1, errDuplicateFloatPoint
|
||||
}
|
||||
pointFound = true
|
||||
continue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
if i+1 >= len(b) {
|
||||
return -1, errUnexpectedFloatEnd
|
||||
}
|
||||
b = b[i+1:]
|
||||
minus := -1
|
||||
switch b[0] {
|
||||
case '+':
|
||||
b = b[1:]
|
||||
minus = 1
|
||||
case '-':
|
||||
b = b[1:]
|
||||
default:
|
||||
minus = 1
|
||||
}
|
||||
vv, err := ParseUint(b)
|
||||
if err != nil {
|
||||
return -1, errInvalidFloatExponent
|
||||
}
|
||||
return float64(v) * offset * math.Pow10(minus*vv), nil
|
||||
}
|
||||
return -1, errUnexpectedFloatChar
|
||||
}
|
||||
v = 10*v + uint64(c-'0')
|
||||
if pointFound {
|
||||
offset /= 10
|
||||
}
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return float64(v) * offset, nil
|
||||
return res, err
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -234,6 +234,12 @@ func TestParseUfloatSuccess(t *testing.T) {
|
||||
testParseUfloatSuccess(t, "1234e2", 1234e2)
|
||||
testParseUfloatSuccess(t, "1234E-5", 1234e-5)
|
||||
testParseUfloatSuccess(t, "1.234e+3", 1.234e+3)
|
||||
testParseUfloatSuccess(t, "1234e23", 1234e23)
|
||||
testParseUfloatSuccess(t, "1.234e+32", 1.234e+32)
|
||||
testParseUfloatSuccess(t, "123456789123456789.987654321", 123456789123456789.987654321)
|
||||
testParseUfloatSuccess(t, "1.23456789123456789987654321", 1.23456789123456789987654321)
|
||||
testParseUfloatSuccess(t, "340282346638528859811704183484516925440", 340282346638528859811704183484516925440)
|
||||
testParseUfloatSuccess(t, "00000000000000000001", 1)
|
||||
}
|
||||
|
||||
func TestParseUfloatError(t *testing.T) {
|
||||
@@ -263,6 +269,10 @@ func TestParseUfloatError(t *testing.T) {
|
||||
|
||||
// missing exponent
|
||||
testParseUfloatError(t, "123534e")
|
||||
|
||||
// negative number
|
||||
testParseUfloatError(t, "-1")
|
||||
testParseUfloatError(t, "-Inf")
|
||||
}
|
||||
|
||||
func testParseUfloatError(t *testing.T, s string) {
|
||||
|
||||
@@ -163,3 +163,27 @@ func BenchmarkAppendUnquotedArgSlowPath(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParseUfloat(b *testing.B) {
|
||||
src := [][]byte{
|
||||
[]byte("0"),
|
||||
[]byte("1234566789."),
|
||||
[]byte(".1234556778"),
|
||||
[]byte("123.456"),
|
||||
[]byte("123456789"),
|
||||
[]byte("1234e23"),
|
||||
[]byte("1234E-51"),
|
||||
[]byte("1.234e+32"),
|
||||
[]byte("123456789123456789.987654321"),
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
for i := range src {
|
||||
_, err := ParseUfloat(src[i])
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user