Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reduce float decoding allocations by using unsafe #39

Merged
merged 3 commits into from Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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