Skip to content

Commit e2bb2e0

Browse files
authored
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
1 parent 7c9c003 commit e2bb2e0

File tree

3 files changed

+43
-52
lines changed

3 files changed

+43
-52
lines changed

bytesconv.go

Lines changed: 9 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"errors"
99
"fmt"
1010
"io"
11-
"math"
1211
"net"
1312
"strconv"
1413
"sync"
@@ -172,61 +171,19 @@ func parseUintBuf(b []byte) (int, int, error) {
172171
return v, n, nil
173172
}
174173

175-
var (
176-
errEmptyFloat = errors.New("empty float number")
177-
errDuplicateFloatPoint = errors.New("duplicate point found in float number")
178-
errUnexpectedFloatEnd = errors.New("unexpected end of float number")
179-
errInvalidFloatExponent = errors.New("invalid float number exponent")
180-
errUnexpectedFloatChar = errors.New("unexpected char found in float number")
181-
)
182-
183174
// ParseUfloat parses unsigned float from buf.
184175
func ParseUfloat(buf []byte) (float64, error) {
185-
if len(buf) == 0 {
186-
return -1, errEmptyFloat
176+
// The implementation of parsing a float string is not easy.
177+
// We believe that the conservative approach is to call strconv.ParseFloat.
178+
// https://github.com/valyala/fasthttp/pull/1865
179+
res, err := strconv.ParseFloat(b2s(buf), 64)
180+
if res < 0 {
181+
return -1, errors.New("negative input is invalid")
187182
}
188-
b := buf
189-
var v uint64
190-
offset := 1.0
191-
var pointFound bool
192-
for i, c := range b {
193-
if c < '0' || c > '9' {
194-
if c == '.' {
195-
if pointFound {
196-
return -1, errDuplicateFloatPoint
197-
}
198-
pointFound = true
199-
continue
200-
}
201-
if c == 'e' || c == 'E' {
202-
if i+1 >= len(b) {
203-
return -1, errUnexpectedFloatEnd
204-
}
205-
b = b[i+1:]
206-
minus := -1
207-
switch b[0] {
208-
case '+':
209-
b = b[1:]
210-
minus = 1
211-
case '-':
212-
b = b[1:]
213-
default:
214-
minus = 1
215-
}
216-
vv, err := ParseUint(b)
217-
if err != nil {
218-
return -1, errInvalidFloatExponent
219-
}
220-
return float64(v) * offset * math.Pow10(minus*vv), nil
221-
}
222-
return -1, errUnexpectedFloatChar
223-
}
224-
v = 10*v + uint64(c-'0')
225-
if pointFound {
226-
offset /= 10
227-
}
183+
if err != nil {
184+
return -1, err
228185
}
229-
return float64(v) * offset, nil
186+
return res, err
230187
}
231188

232189
var (

bytesconv_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ func TestParseUfloatSuccess(t *testing.T) {
234234
testParseUfloatSuccess(t, "1234e2", 1234e2)
235235
testParseUfloatSuccess(t, "1234E-5", 1234e-5)
236236
testParseUfloatSuccess(t, "1.234e+3", 1.234e+3)
237+
testParseUfloatSuccess(t, "1234e23", 1234e23)
238+
testParseUfloatSuccess(t, "1.234e+32", 1.234e+32)
239+
testParseUfloatSuccess(t, "123456789123456789.987654321", 123456789123456789.987654321)
240+
testParseUfloatSuccess(t, "1.23456789123456789987654321", 1.23456789123456789987654321)
241+
testParseUfloatSuccess(t, "340282346638528859811704183484516925440", 340282346638528859811704183484516925440)
242+
testParseUfloatSuccess(t, "00000000000000000001", 1)
237243
}
238244

239245
func TestParseUfloatError(t *testing.T) {
@@ -263,6 +269,10 @@ func TestParseUfloatError(t *testing.T) {
263269

264270
// missing exponent
265271
testParseUfloatError(t, "123534e")
272+
273+
// negative number
274+
testParseUfloatError(t, "-1")
275+
testParseUfloatError(t, "-Inf")
266276
}
267277

268278
func testParseUfloatError(t *testing.T, s string) {

bytesconv_timing_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,27 @@ func BenchmarkAppendUnquotedArgSlowPath(b *testing.B) {
163163
}
164164
})
165165
}
166+
167+
func BenchmarkParseUfloat(b *testing.B) {
168+
src := [][]byte{
169+
[]byte("0"),
170+
[]byte("1234566789."),
171+
[]byte(".1234556778"),
172+
[]byte("123.456"),
173+
[]byte("123456789"),
174+
[]byte("1234e23"),
175+
[]byte("1234E-51"),
176+
[]byte("1.234e+32"),
177+
[]byte("123456789123456789.987654321"),
178+
}
179+
b.RunParallel(func(pb *testing.PB) {
180+
for pb.Next() {
181+
for i := range src {
182+
_, err := ParseUfloat(src[i])
183+
if err != nil {
184+
b.Fatalf("unexpected error: %v", err)
185+
}
186+
}
187+
}
188+
})
189+
}

0 commit comments

Comments
 (0)