Skip to content

Commit 72e9fa8

Browse files
authored
Merge pull request #97 from goccy/feature/fix-interface
Fix interface operation and optimize map operation
2 parents 86ae7d9 + 02797da commit 72e9fa8

File tree

10 files changed

+3142
-3149
lines changed

10 files changed

+3142
-3149
lines changed

benchmarks/encode_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,68 @@ func Benchmark_Encode_LargeStructCached_GoJsonNoEscape(b *testing.B) {
472472
}
473473
}
474474
}
475+
476+
func benchMapValue() map[string]interface{} {
477+
return map[string]interface{}{
478+
"a": 1,
479+
"b": 2.1,
480+
"c": "hello",
481+
"d": struct {
482+
V int
483+
}{
484+
V: 1,
485+
},
486+
"e": true,
487+
}
488+
}
489+
490+
func Benchmark_Encode_MapInterface_EncodingJson(b *testing.B) {
491+
v := benchMapValue()
492+
b.ReportAllocs()
493+
for i := 0; i < b.N; i++ {
494+
if _, err := json.Marshal(v); err != nil {
495+
b.Fatal(err)
496+
}
497+
}
498+
}
499+
500+
func Benchmark_Encode_MapInterface_JsonIter(b *testing.B) {
501+
v := benchMapValue()
502+
var json = jsoniter.ConfigCompatibleWithStandardLibrary
503+
b.ReportAllocs()
504+
for i := 0; i < b.N; i++ {
505+
if _, err := json.Marshal(v); err != nil {
506+
b.Fatal(err)
507+
}
508+
}
509+
}
510+
511+
func Benchmark_Encode_MapInterface_Jettison(b *testing.B) {
512+
v := benchMapValue()
513+
b.ReportAllocs()
514+
for i := 0; i < b.N; i++ {
515+
if _, err := jettison.Marshal(v); err != nil {
516+
b.Fatal(err)
517+
}
518+
}
519+
}
520+
521+
func Benchmark_Encode_MapInterface_SegmentioJson(b *testing.B) {
522+
v := benchMapValue()
523+
b.ReportAllocs()
524+
for i := 0; i < b.N; i++ {
525+
if _, err := segmentiojson.Marshal(v); err != nil {
526+
b.Fatal(err)
527+
}
528+
}
529+
}
530+
531+
func Benchmark_Encode_MapInterface_GoJson(b *testing.B) {
532+
v := benchMapValue()
533+
b.ReportAllocs()
534+
for i := 0; i < b.N; i++ {
535+
if _, err := gojson.Marshal(v); err != nil {
536+
b.Fatal(err)
537+
}
538+
}
539+
}

cmd/generator/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,6 @@ func (t opType) fieldToStringTagField() opType {
315315
opTypes := []opType{
316316
createOpType("End", "Op"),
317317
createOpType("Interface", "Op"),
318-
createOpType("InterfaceEnd", "Op"),
319318
createOpType("Ptr", "Op"),
320319
createOpType("NPtr", "Op"),
321320
createOpType("SliceHead", "SliceHead"),

encode.go

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Encoder struct {
2222
enabledIndent bool
2323
enabledHTMLEscape bool
2424
unorderedMap bool
25+
baseIndent int
2526
prefix []byte
2627
indentStr []byte
2728
}
@@ -112,6 +113,7 @@ func (e *Encoder) EncodeWithOption(v interface{}, opts ...EncodeOption) error {
112113
}
113114
}
114115
header := (*interfaceHeader)(unsafe.Pointer(&v))
116+
e.ptr = header.ptr
115117
buf, err := e.encode(header, v == nil)
116118
if err != nil {
117119
return err
@@ -155,6 +157,7 @@ func (e *Encoder) release() {
155157
}
156158

157159
func (e *Encoder) reset() {
160+
e.baseIndent = 0
158161
e.enabledHTMLEscape = true
159162
e.enabledIndent = false
160163
e.unorderedMap = false
@@ -192,23 +195,31 @@ func (e *Encoder) encode(header *interfaceHeader, isNil bool) ([]byte, error) {
192195
typ := header.typ
193196

194197
typeptr := uintptr(unsafe.Pointer(typ))
195-
opcodeMap := loadOpcodeMap()
196-
if codeSet, exists := opcodeMap[typeptr]; exists {
197-
ctx := e.ctx
198-
p := uintptr(header.ptr)
199-
ctx.init(p, codeSet.codeLength)
198+
codeSet, err := e.compileToGetCodeSet(typeptr)
199+
if err != nil {
200+
return nil, err
201+
}
200202

201-
if e.enabledIndent {
202-
if e.enabledHTMLEscape {
203-
return e.runEscapedIndent(ctx, b, codeSet.code)
204-
} else {
205-
return e.runIndent(ctx, b, codeSet.code)
206-
}
207-
}
203+
ctx := e.ctx
204+
p := uintptr(header.ptr)
205+
ctx.init(p, codeSet.codeLength)
206+
if e.enabledIndent {
208207
if e.enabledHTMLEscape {
209-
return e.runEscaped(ctx, b, codeSet.code)
208+
return e.runEscapedIndent(ctx, b, codeSet)
209+
} else {
210+
return e.runIndent(ctx, b, codeSet)
210211
}
211-
return e.run(ctx, b, codeSet.code)
212+
}
213+
if e.enabledHTMLEscape {
214+
return e.runEscaped(ctx, b, codeSet)
215+
}
216+
return e.run(ctx, b, codeSet)
217+
}
218+
219+
func (e *Encoder) compileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) {
220+
opcodeMap := loadOpcodeMap()
221+
if codeSet, exists := opcodeMap[typeptr]; exists {
222+
return codeSet, nil
212223
}
213224

214225
// noescape trick for header.typ ( reflect.*rtype )
@@ -230,21 +241,7 @@ func (e *Encoder) encode(header *interfaceHeader, isNil bool) ([]byte, error) {
230241
}
231242

232243
storeOpcodeSet(typeptr, codeSet, opcodeMap)
233-
p := uintptr(header.ptr)
234-
ctx := e.ctx
235-
ctx.init(p, codeLength)
236-
237-
if e.enabledIndent {
238-
if e.enabledHTMLEscape {
239-
return e.runEscapedIndent(ctx, b, codeSet.code)
240-
} else {
241-
return e.runIndent(ctx, b, codeSet.code)
242-
}
243-
}
244-
if e.enabledHTMLEscape {
245-
return e.runEscaped(ctx, b, codeSet.code)
246-
}
247-
return e.run(ctx, b, codeSet.code)
244+
return codeSet, nil
248245
}
249246

250247
func encodeFloat32(b []byte, v float32) []byte {
@@ -303,7 +300,7 @@ func appendStructEnd(b []byte) []byte {
303300
func (e *Encoder) appendStructEndIndent(b []byte, indent int) []byte {
304301
b = append(b, '\n')
305302
b = append(b, e.prefix...)
306-
b = append(b, bytes.Repeat(e.indentStr, indent)...)
303+
b = append(b, bytes.Repeat(e.indentStr, e.baseIndent+indent)...)
307304
return append(b, '}', ',', '\n')
308305
}
309306

@@ -324,5 +321,5 @@ func encodeByteSlice(b []byte, src []byte) []byte {
324321

325322
func (e *Encoder) encodeIndent(b []byte, indent int) []byte {
326323
b = append(b, e.prefix...)
327-
return append(b, bytes.Repeat(e.indentStr, indent)...)
324+
return append(b, bytes.Repeat(e.indentStr, e.baseIndent+indent)...)
328325
}

encode_context.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,67 @@
11
package json
22

33
import (
4+
"bytes"
5+
"sync"
46
"unsafe"
57
)
68

9+
type mapItem struct {
10+
key []byte
11+
value []byte
12+
}
13+
14+
type mapslice struct {
15+
items []mapItem
16+
}
17+
18+
func (m *mapslice) Len() int {
19+
return len(m.items)
20+
}
21+
22+
func (m *mapslice) Less(i, j int) bool {
23+
return bytes.Compare(m.items[i].key, m.items[j].key) < 0
24+
}
25+
26+
func (m *mapslice) Swap(i, j int) {
27+
m.items[i], m.items[j] = m.items[j], m.items[i]
28+
}
29+
30+
type encodeMapContext struct {
31+
iter unsafe.Pointer
32+
pos []int
33+
slice *mapslice
34+
buf []byte
35+
}
36+
37+
var mapContextPool = sync.Pool{
38+
New: func() interface{} {
39+
return &encodeMapContext{}
40+
},
41+
}
42+
43+
func newMapContext(mapLen int) *encodeMapContext {
44+
ctx := mapContextPool.Get().(*encodeMapContext)
45+
if ctx.slice == nil {
46+
ctx.slice = &mapslice{
47+
items: make([]mapItem, 0, mapLen),
48+
}
49+
}
50+
if cap(ctx.pos) < (mapLen*2 + 1) {
51+
ctx.pos = make([]int, 0, mapLen*2+1)
52+
ctx.slice.items = make([]mapItem, 0, mapLen)
53+
} else {
54+
ctx.pos = ctx.pos[:0]
55+
ctx.slice.items = ctx.slice.items[:0]
56+
}
57+
ctx.buf = ctx.buf[:0]
58+
return ctx
59+
}
60+
61+
func releaseMapContext(c *encodeMapContext) {
62+
mapContextPool.Put(c)
63+
}
64+
765
type encodeCompileContext struct {
866
typ *rtype
967
root bool

encode_opcode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (c *opcode) totalLength() int {
131131
var idx int
132132
for code := c; code.op != opEnd; {
133133
idx = int(code.idx / uintptrSize)
134-
if code.op == opInterfaceEnd || code.op == opStructFieldRecursiveEnd {
134+
if code.op == opStructFieldRecursiveEnd {
135135
break
136136
}
137137
switch code.op.codeType() {

0 commit comments

Comments
 (0)