Skip to content

Commit b05a0c3

Browse files
authored
Merge pull request #242 from goccy/feature/improve-decoder-performance
Add DecodeFieldPriorityFirstWin option
2 parents 7d7af9f + 8a3ef38 commit b05a0c3

25 files changed

+327
-117
lines changed

benchmarks/decode_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,34 @@ func Benchmark_Decode_LargeStruct_Unmarshal_GoJsonNoEscape(b *testing.B) {
375375
}
376376
}
377377

378+
func Benchmark_Decode_LargeStruct_Unmarshal_GoJsonFirstWinMode(b *testing.B) {
379+
b.ReportAllocs()
380+
for i := 0; i < b.N; i++ {
381+
result := LargePayload{}
382+
if err := gojson.UnmarshalWithOption(
383+
LargeFixture,
384+
&result,
385+
gojson.DecodeFieldPriorityFirstWin(),
386+
); err != nil {
387+
b.Fatal(err)
388+
}
389+
}
390+
}
391+
392+
func Benchmark_Decode_LargeStruct_Unmarshal_GoJsonNoEscapeFirstWinMode(b *testing.B) {
393+
b.ReportAllocs()
394+
for i := 0; i < b.N; i++ {
395+
result := LargePayload{}
396+
if err := gojson.UnmarshalNoEscape(
397+
LargeFixture,
398+
&result,
399+
gojson.DecodeFieldPriorityFirstWin(),
400+
); err != nil {
401+
b.Fatal(err)
402+
}
403+
}
404+
}
405+
378406
func Benchmark_Decode_LargeStruct_Stream_EncodingJson(b *testing.B) {
379407
b.ReportAllocs()
380408
reader := bytes.NewReader(LargeFixture)
@@ -434,3 +462,18 @@ func Benchmark_Decode_LargeStruct_Stream_GoJson(b *testing.B) {
434462
}
435463
}
436464
}
465+
466+
func Benchmark_Decode_LargeStruct_Stream_GoJsonFirstWinMode(b *testing.B) {
467+
b.ReportAllocs()
468+
reader := bytes.NewReader(LargeFixture)
469+
for i := 0; i < b.N; i++ {
470+
result := LargePayload{}
471+
reader.Reset(LargeFixture)
472+
if err := gojson.NewDecoder(reader).DecodeWithOption(
473+
&result,
474+
gojson.DecodeFieldPriorityFirstWin(),
475+
); err != nil {
476+
b.Fatal(err)
477+
}
478+
}
479+
}

decode.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type emptyInterface struct {
2424
ptr unsafe.Pointer
2525
}
2626

27-
func unmarshal(data []byte, v interface{}) error {
27+
func unmarshal(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
2828
src := make([]byte, len(data)+1) // append nul byte to the end
2929
copy(src, data)
3030

@@ -37,14 +37,22 @@ func unmarshal(data []byte, v interface{}) error {
3737
if err != nil {
3838
return err
3939
}
40-
cursor, err := dec.Decode(src, 0, 0, header.ptr)
40+
ctx := decoder.TakeRuntimeContext()
41+
ctx.Buf = src
42+
ctx.Option.Flag = 0
43+
for _, optFunc := range optFuncs {
44+
optFunc(ctx.Option)
45+
}
46+
cursor, err := dec.Decode(ctx, 0, 0, header.ptr)
4147
if err != nil {
48+
decoder.ReleaseRuntimeContext(ctx)
4249
return err
4350
}
51+
decoder.ReleaseRuntimeContext(ctx)
4452
return validateEndBuf(src, cursor)
4553
}
4654

47-
func unmarshalNoEscape(data []byte, v interface{}) error {
55+
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
4856
src := make([]byte, len(data)+1) // append nul byte to the end
4957
copy(src, data)
5058

@@ -57,10 +65,19 @@ func unmarshalNoEscape(data []byte, v interface{}) error {
5765
if err != nil {
5866
return err
5967
}
60-
cursor, err := dec.Decode(src, 0, 0, noescape(header.ptr))
68+
69+
ctx := decoder.TakeRuntimeContext()
70+
ctx.Buf = src
71+
ctx.Option.Flag = 0
72+
for _, optFunc := range optFuncs {
73+
optFunc(ctx.Option)
74+
}
75+
cursor, err := dec.Decode(ctx, 0, 0, noescape(header.ptr))
6176
if err != nil {
77+
decoder.ReleaseRuntimeContext(ctx)
6278
return err
6379
}
80+
decoder.ReleaseRuntimeContext(ctx)
6481
return validateEndBuf(src, cursor)
6582
}
6683

@@ -117,6 +134,10 @@ func (d *Decoder) Buffered() io.Reader {
117134
// See the documentation for Unmarshal for details about
118135
// the conversion of JSON into a Go value.
119136
func (d *Decoder) Decode(v interface{}) error {
137+
return d.DecodeWithOption(v)
138+
}
139+
140+
func (d *Decoder) DecodeWithOption(v interface{}, optFuncs ...DecodeOptionFunc) error {
120141
header := (*emptyInterface)(unsafe.Pointer(&v))
121142
typ := header.typ
122143
ptr := uintptr(header.ptr)
@@ -136,6 +157,9 @@ func (d *Decoder) Decode(v interface{}) error {
136157
return err
137158
}
138159
s := d.s
160+
for _, optFunc := range optFuncs {
161+
optFunc(s.Option)
162+
}
139163
if err := dec.DecodeStream(s, 0, header.ptr); err != nil {
140164
return err
141165
}

internal/decoder/anonymous_field.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ func (d *anonymousFieldDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Po
2828
return d.dec.DecodeStream(s, depth, unsafe.Pointer(uintptr(p)+d.offset))
2929
}
3030

31-
func (d *anonymousFieldDecoder) Decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
31+
func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
3232
if *(*unsafe.Pointer)(p) == nil {
3333
*(*unsafe.Pointer)(p) = unsafe_New(d.structType)
3434
}
3535
p = *(*unsafe.Pointer)(p)
36-
return d.dec.Decode(buf, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
36+
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
3737
}

internal/decoder/array.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ func (d *arrayDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) er
5858
}
5959
}
6060
idx++
61-
s.skipWhiteSpace()
62-
switch s.char() {
61+
switch s.skipWhiteSpace() {
6362
case ']':
6463
for idx < d.alen {
6564
*(*unsafe.Pointer)(unsafe.Pointer(uintptr(p) + uintptr(idx)*d.size)) = d.zeroValue
@@ -92,7 +91,8 @@ ERROR:
9291
return errors.ErrUnexpectedEndOfJSON("array", s.totalOffset())
9392
}
9493

95-
func (d *arrayDecoder) Decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
94+
func (d *arrayDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
95+
buf := ctx.Buf
9696
depth++
9797
if depth > maxDecodeNestingDepth {
9898
return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
@@ -114,7 +114,7 @@ func (d *arrayDecoder) Decode(buf []byte, cursor, depth int64, p unsafe.Pointer)
114114
for {
115115
cursor++
116116
if idx < d.alen {
117-
c, err := d.valueDecoder.Decode(buf, cursor, depth, unsafe.Pointer(uintptr(p)+uintptr(idx)*d.size))
117+
c, err := d.valueDecoder.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+uintptr(idx)*d.size))
118118
if err != nil {
119119
return 0, err
120120
}

internal/decoder/bool.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ func newBoolDecoder(structName, fieldName string) *boolDecoder {
1616
}
1717

1818
func (d *boolDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
19-
s.skipWhiteSpace()
19+
c := s.skipWhiteSpace()
2020
for {
21-
switch s.char() {
21+
switch c {
2222
case 't':
2323
if err := trueBytes(s); err != nil {
2424
return err
@@ -38,6 +38,7 @@ func (d *boolDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) err
3838
return nil
3939
case nul:
4040
if s.read() {
41+
c = s.char()
4142
continue
4243
}
4344
goto ERROR
@@ -48,7 +49,8 @@ ERROR:
4849
return errors.ErrUnexpectedEndOfJSON("bool", s.totalOffset())
4950
}
5051

51-
func (d *boolDecoder) Decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
52+
func (d *boolDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
53+
buf := ctx.Buf
5254
cursor = skipWhiteSpace(buf, cursor)
5355
switch buf[cursor] {
5456
case 't':

internal/decoder/bytes.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ func (d *bytesDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) er
5757
return nil
5858
}
5959

60-
func (d *bytesDecoder) Decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
61-
bytes, c, err := d.decodeBinary(buf, cursor, depth, p)
60+
func (d *bytesDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
61+
bytes, c, err := d.decodeBinary(ctx, cursor, depth, p)
6262
if err != nil {
6363
return 0, err
6464
}
@@ -131,7 +131,8 @@ func (d *bytesDecoder) decodeStreamBinary(s *Stream, depth int64, p unsafe.Point
131131
return nil, errors.ErrNotAtBeginningOfValue(s.totalOffset())
132132
}
133133

134-
func (d *bytesDecoder) decodeBinary(buf []byte, cursor, depth int64, p unsafe.Pointer) ([]byte, int64, error) {
134+
func (d *bytesDecoder) decodeBinary(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) ([]byte, int64, error) {
135+
buf := ctx.Buf
135136
for {
136137
switch buf[cursor] {
137138
case ' ', '\n', '\t', '\r':
@@ -157,7 +158,7 @@ func (d *bytesDecoder) decodeBinary(buf []byte, cursor, depth int64, p unsafe.Po
157158
Offset: cursor,
158159
}
159160
}
160-
c, err := d.sliceDecoder.Decode(buf, cursor, depth, p)
161+
c, err := d.sliceDecoder.Decode(ctx, cursor, depth, p)
161162
if err != nil {
162163
return nil, 0, err
163164
}

internal/decoder/context.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
11
package decoder
22

33
import (
4+
"sync"
45
"unsafe"
56

67
"github.com/goccy/go-json/internal/errors"
78
)
89

10+
type RuntimeContext struct {
11+
Buf []byte
12+
Option *Option
13+
}
14+
15+
var (
16+
runtimeContextPool = sync.Pool{
17+
New: func() interface{} {
18+
return &RuntimeContext{
19+
Option: &Option{},
20+
}
21+
},
22+
}
23+
)
24+
25+
func TakeRuntimeContext() *RuntimeContext {
26+
return runtimeContextPool.Get().(*RuntimeContext)
27+
}
28+
29+
func ReleaseRuntimeContext(ctx *RuntimeContext) {
30+
runtimeContextPool.Put(ctx)
31+
}
32+
933
var (
1034
isWhiteSpace = [256]bool{}
1135
)

internal/decoder/float.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ func (d *floatDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) er
135135
return nil
136136
}
137137

138-
func (d *floatDecoder) Decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
138+
func (d *floatDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
139+
buf := ctx.Buf
139140
bytes, c, err := d.decodeByte(buf, cursor)
140141
if err != nil {
141142
return 0, err

internal/decoder/int.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ func (d *intDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
209209
return nil
210210
}
211211

212-
func (d *intDecoder) Decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
213-
bytes, c, err := d.decodeByte(buf, cursor)
212+
func (d *intDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
213+
bytes, c, err := d.decodeByte(ctx.Buf, cursor)
214214
if err != nil {
215215
return 0, err
216216
}

0 commit comments

Comments
 (0)