From e2bb2e0d64cfde45313daf361a8cb725569f0e91 Mon Sep 17 00:00:00 2001 From: Kashiwa <13825170+ksw2000@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:46:29 +0800 Subject: [PATCH] Improve performance of ParseUfloat (#1865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- bytesconv.go | 61 ++++++---------------------------------- bytesconv_test.go | 10 +++++++ bytesconv_timing_test.go | 24 ++++++++++++++++ 3 files changed, 43 insertions(+), 52 deletions(-) diff --git a/bytesconv.go b/bytesconv.go index f01a6c11e8..053da6a700 100644 --- a/bytesconv.go +++ b/bytesconv.go @@ -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 ( diff --git a/bytesconv_test.go b/bytesconv_test.go index d5ca5e4341..b9a2dbfb08 100644 --- a/bytesconv_test.go +++ b/bytesconv_test.go @@ -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) { diff --git a/bytesconv_timing_test.go b/bytesconv_timing_test.go index 334376a94d..e9626c54a8 100644 --- a/bytesconv_timing_test.go +++ b/bytesconv_timing_test.go @@ -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) + } + } + } + }) +}