Skip to content

Commit

Permalink
Merge pull request #39 from tdakkota/feat/reduce-float-allocation
Browse files Browse the repository at this point in the history
feat: reduce float decoding allocations by using unsafe
  • Loading branch information
ernado committed Mar 17, 2022
2 parents 2233db5 + 9917dfa commit 232c247
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 26 deletions.
20 changes: 0 additions & 20 deletions dec_float.go
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"io"
"math/big"
"strconv"

"github.com/go-faster/errors"
)
Expand Down Expand Up @@ -309,25 +308,6 @@ NonDecimal:
return d.float64Slow()
}

func (d *Decoder) floatSlow(size int) (float64, error) {
var buf [32]byte

str, err := d.numberAppend(buf[:0])
if err != nil {
return 0, errors.Wrap(err, "number")
}
if err := validateFloat(str); err != nil {
return 0, errors.Wrap(err, "invalid")
}

val, err := strconv.ParseFloat(string(str), size)
if err != nil {
return 0, err
}

return val, nil
}

func (d *Decoder) float64Slow() (float64, error) { return d.floatSlow(size64) }

func validateFloat(str []byte) error {
Expand Down
13 changes: 7 additions & 6 deletions dec_float_test.go
Expand Up @@ -2,6 +2,7 @@ package jx

import (
"bytes"
"fmt"
"io"
"testing"

Expand Down Expand Up @@ -90,7 +91,7 @@ func TestDecoder_Float32(t *testing.T) {
}

func TestDecoder_Float64(t *testing.T) {
for _, tc := range []struct {
for i, tc := range []struct {
String string
Value float64
}{
Expand All @@ -103,29 +104,29 @@ func TestDecoder_Float64(t *testing.T) {
Value: 18446744073709551.7000,
},
} {
t.Run(tc.String, func(t *testing.T) {
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
t.Run("32Str", func(t *testing.T) {
v, err := DecodeStr(tc.String).Float32()
require.InEpsilon(t, tc.Value, v, epsilon)
require.InEpsilonf(t, tc.Value, v, epsilon, "%v != %v", tc.Value, v)
require.NoError(t, err)
})
t.Run("64Str", func(t *testing.T) {
v, err := DecodeStr(tc.String).Float64()
require.InEpsilon(t, tc.Value, v, epsilon)
require.InEpsilonf(t, tc.Value, v, epsilon, "%v != %v", tc.Value, v)
require.NoError(t, err)
})
t.Run("32", func(t *testing.T) {
decodeStr(t, tc.String, func(d *Decoder) {
v, err := d.Float32()
require.NoError(t, err)
require.InEpsilon(t, tc.Value, v, epsilon)
require.InEpsilonf(t, tc.Value, v, epsilon, "%v != %v", tc.Value, v)
})
})
t.Run("64", func(t *testing.T) {
decodeStr(t, tc.String, func(d *Decoder) {
v, err := d.Float64()
require.NoError(t, err)
require.InEpsilon(t, tc.Value, v, epsilon)
require.InEpsilonf(t, tc.Value, v, epsilon, "%v != %v", tc.Value, v)
})
})
})
Expand Down
28 changes: 28 additions & 0 deletions dec_parse_float_safe.go
@@ -0,0 +1,28 @@
//go:build appengine || purego

package jx

import (
"strconv"

"github.com/go-faster/errors"
)

func (d *Decoder) floatSlow(size int) (float64, error) {
var buf [32]byte

str, err := d.numberAppend(buf[:0])
if err != nil {
return 0, errors.Wrap(err, "number")
}
if err := validateFloat(str); err != nil {
return 0, errors.Wrap(err, "invalid")
}

val, err := strconv.ParseFloat(string(str), size)
if err != nil {
return 0, err
}

return val, nil
}
34 changes: 34 additions & 0 deletions dec_parse_float_unsafe.go
@@ -0,0 +1,34 @@
//go:build !appengine && !purego

package jx

import (
"strconv"
"unsafe"

"github.com/go-faster/errors"
)

func (d *Decoder) floatSlow(size int) (float64, error) {
var buf [32]byte

str, err := d.numberAppend(buf[:0])
if err != nil {
return 0, errors.Wrap(err, "number")
}
if err := validateFloat(str); err != nil {
return 0, errors.Wrap(err, "invalid")
}

slice := *(*sliceType)(unsafe.Pointer(&str)) // #nosec G103
s := strType{
Ptr: noescape(slice.Ptr),
Len: slice.Len,
}
val, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&s)), size) // #nosec G103
if err != nil {
return 0, err
}

return val, nil
}
3 changes: 3 additions & 0 deletions go.test.sh
Expand Up @@ -5,5 +5,8 @@ set -e
echo "test"
go test --timeout 5m ./...

echo "test purego"
go test --timeout 5m -tags purego ./...

echo "test -race"
go test --timeout 5m -race ./...
19 changes: 19 additions & 0 deletions runtime.go
@@ -0,0 +1,19 @@
//go:build !appengine && !purego

package jx

import "unsafe"

type sliceType struct {
Ptr unsafe.Pointer
Len uintptr
Cap uintptr
}

type strType struct {
Ptr unsafe.Pointer
Len uintptr
}

//go:linkname noescape runtime.noescape
func noescape(unsafe.Pointer) unsafe.Pointer

0 comments on commit 232c247

Please sign in to comment.