Skip to content

Commit

Permalink
Improve performance of ParseUfloat (#1865)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ksw2000 authored Sep 22, 2024
1 parent 7c9c003 commit e2bb2e0
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 52 deletions.
61 changes: 9 additions & 52 deletions bytesconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io"
"math"
"net"
"strconv"
"sync"
Expand Down Expand Up @@ -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 (
Expand Down
10 changes: 10 additions & 0 deletions bytesconv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 24 additions & 0 deletions bytesconv_timing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
})
}

0 comments on commit e2bb2e0

Please sign in to comment.