mirror of https://github.com/goccy/go-json.git
Improve encoding performance
This commit is contained in:
parent
26e4f7f455
commit
f5daa592fa
69
encode.go
69
encode.go
|
@ -15,16 +15,15 @@ import (
|
||||||
|
|
||||||
// An Encoder writes JSON values to an output stream.
|
// An Encoder writes JSON values to an output stream.
|
||||||
type Encoder struct {
|
type Encoder struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
ctx *encodeRuntimeContext
|
ctx *encodeRuntimeContext
|
||||||
buf []byte
|
ptr unsafe.Pointer
|
||||||
enabledIndent bool
|
buf []byte
|
||||||
enabledHTMLEscape bool
|
enabledIndent bool
|
||||||
unorderedMap bool
|
enabledHTMLEscape bool
|
||||||
prefix []byte
|
unorderedMap bool
|
||||||
indentStr []byte
|
prefix []byte
|
||||||
structTypeToCompiledCode map[uintptr]*compiledCode
|
indentStr []byte
|
||||||
structTypeToCompiledIndentCode map[uintptr]*compiledCode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type compiledCode struct {
|
type compiledCode struct {
|
||||||
|
@ -73,9 +72,7 @@ func init() {
|
||||||
ptrs: make([]uintptr, 128),
|
ptrs: make([]uintptr, 128),
|
||||||
keepRefs: make([]unsafe.Pointer, 0, 8),
|
keepRefs: make([]unsafe.Pointer, 0, 8),
|
||||||
},
|
},
|
||||||
buf: make([]byte, 0, bufSize),
|
buf: make([]byte, 0, bufSize),
|
||||||
structTypeToCompiledCode: map[uintptr]*compiledCode{},
|
|
||||||
structTypeToCompiledIndentCode: map[uintptr]*compiledCode{},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -105,20 +102,20 @@ func (e *Encoder) EncodeWithOption(v interface{}, opts ...EncodeOption) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var err error
|
buf, err := e.encode(v)
|
||||||
if e.buf, err = e.encode(v); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if e.enabledIndent {
|
if e.enabledIndent {
|
||||||
e.buf = e.buf[:len(e.buf)-2]
|
buf = buf[:len(buf)-2]
|
||||||
} else {
|
} else {
|
||||||
e.buf = e.buf[:len(e.buf)-1]
|
buf = buf[:len(buf)-1]
|
||||||
}
|
}
|
||||||
e.buf = append(e.buf, '\n')
|
buf = append(buf, '\n')
|
||||||
if _, err := e.w.Write(e.buf); err != nil {
|
if _, err := e.w.Write(buf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
e.buf = e.buf[:0]
|
e.buf = buf[:0]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,29 +145,31 @@ func (e *Encoder) release() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) reset() {
|
func (e *Encoder) reset() {
|
||||||
e.buf = e.buf[:0]
|
|
||||||
e.enabledHTMLEscape = true
|
e.enabledHTMLEscape = true
|
||||||
e.enabledIndent = false
|
e.enabledIndent = false
|
||||||
e.unorderedMap = false
|
e.unorderedMap = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) {
|
func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) {
|
||||||
var err error
|
buf, err := e.encode(v)
|
||||||
if e.buf, err = e.encode(v); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.buf = buf
|
||||||
|
|
||||||
if e.enabledIndent {
|
if e.enabledIndent {
|
||||||
copied := make([]byte, len(e.buf)-2)
|
copied := make([]byte, len(buf)-2)
|
||||||
copy(copied, e.buf)
|
copy(copied, buf)
|
||||||
return copied, nil
|
return copied, nil
|
||||||
}
|
}
|
||||||
copied := make([]byte, len(e.buf)-1)
|
copied := make([]byte, len(buf)-1)
|
||||||
copy(copied, e.buf)
|
copy(copied, buf)
|
||||||
return copied, nil
|
return copied, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) encode(v interface{}) ([]byte, error) {
|
func (e *Encoder) encode(v interface{}) ([]byte, error) {
|
||||||
b := e.buf
|
b := e.buf[:0]
|
||||||
if v == nil {
|
if v == nil {
|
||||||
b = encodeNull(b)
|
b = encodeNull(b)
|
||||||
if e.enabledIndent {
|
if e.enabledIndent {
|
||||||
|
@ -202,17 +201,19 @@ func (e *Encoder) encode(v interface{}) ([]byte, error) {
|
||||||
copiedType := *(**rtype)(unsafe.Pointer(&typeptr))
|
copiedType := *(**rtype)(unsafe.Pointer(&typeptr))
|
||||||
|
|
||||||
codeIndent, err := e.compileHead(&encodeCompileContext{
|
codeIndent, err := e.compileHead(&encodeCompileContext{
|
||||||
typ: copiedType,
|
typ: copiedType,
|
||||||
root: true,
|
root: true,
|
||||||
withIndent: true,
|
withIndent: true,
|
||||||
|
structTypeToCompiledCode: map[uintptr]*compiledCode{},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
code, err := e.compileHead(&encodeCompileContext{
|
code, err := e.compileHead(&encodeCompileContext{
|
||||||
typ: copiedType,
|
typ: copiedType,
|
||||||
root: true,
|
root: true,
|
||||||
withIndent: false,
|
withIndent: false,
|
||||||
|
structTypeToCompiledCode: map[uintptr]*compiledCode{},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package json
|
package json
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -877,14 +876,8 @@ func (e *Encoder) recursiveCode(ctx *encodeCompileContext, jmp *compiledCode) *o
|
||||||
func (e *Encoder) compiledCode(ctx *encodeCompileContext) *opcode {
|
func (e *Encoder) compiledCode(ctx *encodeCompileContext) *opcode {
|
||||||
typ := ctx.typ
|
typ := ctx.typ
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
if ctx.withIndent {
|
if compiledCode, exists := ctx.structTypeToCompiledCode[typeptr]; exists {
|
||||||
if compiledCode, exists := e.structTypeToCompiledIndentCode[typeptr]; exists {
|
return e.recursiveCode(ctx, compiledCode)
|
||||||
return e.recursiveCode(ctx, compiledCode)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if compiledCode, exists := e.structTypeToCompiledCode[typeptr]; exists {
|
|
||||||
return e.recursiveCode(ctx, compiledCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1140,11 +1133,7 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
|
||||||
typ := ctx.typ
|
typ := ctx.typ
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
compiled := &compiledCode{}
|
compiled := &compiledCode{}
|
||||||
if ctx.withIndent {
|
ctx.structTypeToCompiledCode[typeptr] = compiled
|
||||||
e.structTypeToCompiledIndentCode[typeptr] = compiled
|
|
||||||
} else {
|
|
||||||
e.structTypeToCompiledCode[typeptr] = compiled
|
|
||||||
}
|
|
||||||
// header => code => structField => code => end
|
// header => code => structField => code => end
|
||||||
// ^ |
|
// ^ |
|
||||||
// |__________|
|
// |__________|
|
||||||
|
@ -1212,12 +1201,7 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf(`"%s":`, tag.key)
|
key := fmt.Sprintf(`"%s":`, tag.key)
|
||||||
|
escapedKey := fmt.Sprintf(`%s:`, string(encodeEscapedString([]byte{}, tag.key)))
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := NewEncoder(&buf)
|
|
||||||
enc.buf = encodeEscapedString(enc.buf, tag.key)
|
|
||||||
escapedKey := fmt.Sprintf(`%s:`, string(enc.buf))
|
|
||||||
enc.release()
|
|
||||||
fieldCode := &opcode{
|
fieldCode := &opcode{
|
||||||
typ: valueCode.typ,
|
typ: valueCode.typ,
|
||||||
displayIdx: fieldOpcodeIndex,
|
displayIdx: fieldOpcodeIndex,
|
||||||
|
@ -1294,11 +1278,7 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
|
||||||
ret := (*opcode)(unsafe.Pointer(head))
|
ret := (*opcode)(unsafe.Pointer(head))
|
||||||
compiled.code = ret
|
compiled.code = ret
|
||||||
|
|
||||||
if ctx.withIndent {
|
delete(ctx.structTypeToCompiledCode, typeptr)
|
||||||
delete(e.structTypeToCompiledIndentCode, typeptr)
|
|
||||||
} else {
|
|
||||||
delete(e.structTypeToCompiledCode, typeptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,25 +5,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type encodeCompileContext struct {
|
type encodeCompileContext struct {
|
||||||
typ *rtype
|
typ *rtype
|
||||||
withIndent bool
|
withIndent bool
|
||||||
root bool
|
root bool
|
||||||
opcodeIndex int
|
opcodeIndex int
|
||||||
ptrIndex int
|
ptrIndex int
|
||||||
indent int
|
indent int
|
||||||
|
structTypeToCompiledCode map[uintptr]*compiledCode
|
||||||
|
|
||||||
parent *encodeCompileContext
|
parent *encodeCompileContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *encodeCompileContext) context() *encodeCompileContext {
|
func (c *encodeCompileContext) context() *encodeCompileContext {
|
||||||
return &encodeCompileContext{
|
return &encodeCompileContext{
|
||||||
typ: c.typ,
|
typ: c.typ,
|
||||||
withIndent: c.withIndent,
|
withIndent: c.withIndent,
|
||||||
root: c.root,
|
root: c.root,
|
||||||
opcodeIndex: c.opcodeIndex,
|
opcodeIndex: c.opcodeIndex,
|
||||||
ptrIndex: c.ptrIndex,
|
ptrIndex: c.ptrIndex,
|
||||||
indent: c.indent,
|
indent: c.indent,
|
||||||
parent: c,
|
structTypeToCompiledCode: c.structTypeToCompiledCode,
|
||||||
|
parent: c,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
37
encode_vm.go
37
encode_vm.go
|
@ -317,10 +317,11 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c, err := e.compileHead(&encodeCompileContext{
|
c, err := e.compileHead(&encodeCompileContext{
|
||||||
typ: header.typ,
|
typ: header.typ,
|
||||||
root: code.root,
|
root: code.root,
|
||||||
withIndent: e.enabledIndent,
|
withIndent: e.enabledIndent,
|
||||||
indent: code.indent,
|
indent: code.indent,
|
||||||
|
structTypeToCompiledCode: map[uintptr]*compiledCode{},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -4157,17 +4158,13 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
|
||||||
} else {
|
} else {
|
||||||
b = append(b, '{')
|
b = append(b, '{')
|
||||||
b = e.encodeKey(b, code)
|
b = e.encodeKey(b, code)
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := NewEncoder(&buf)
|
|
||||||
s := e.ptrToString(ptr + code.offset)
|
s := e.ptrToString(ptr + code.offset)
|
||||||
if e.enabledHTMLEscape {
|
if e.enabledHTMLEscape {
|
||||||
enc.buf = encodeEscapedString(enc.buf, s)
|
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
|
||||||
} else {
|
} else {
|
||||||
enc.buf = encodeNoEscapedString(enc.buf, s)
|
b = e.encodeString(b, string(encodeNoEscapedString([]byte{}, s)))
|
||||||
}
|
}
|
||||||
b = e.encodeString(b, string(enc.buf))
|
|
||||||
b = encodeComma(b)
|
b = encodeComma(b)
|
||||||
enc.release()
|
|
||||||
code = code.next
|
code = code.next
|
||||||
}
|
}
|
||||||
case opStructFieldPtrAnonymousHeadStringTagString:
|
case opStructFieldPtrAnonymousHeadStringTagString:
|
||||||
|
@ -4706,15 +4703,12 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
|
||||||
b = e.encodeIndent(b, code.indent+1)
|
b = e.encodeIndent(b, code.indent+1)
|
||||||
b = e.encodeKey(b, code)
|
b = e.encodeKey(b, code)
|
||||||
b = append(b, ' ')
|
b = append(b, ' ')
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := NewEncoder(&buf)
|
|
||||||
s := e.ptrToString(ptr + code.offset)
|
s := e.ptrToString(ptr + code.offset)
|
||||||
if e.enabledHTMLEscape {
|
if e.enabledHTMLEscape {
|
||||||
enc.buf = encodeEscapedString(enc.buf, s)
|
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
|
||||||
} else {
|
} else {
|
||||||
enc.buf = encodeNoEscapedString(enc.buf, s)
|
b = e.encodeString(b, string(encodeNoEscapedString([]byte{}, s)))
|
||||||
}
|
}
|
||||||
b = e.encodeString(b, string(enc.buf))
|
|
||||||
b = encodeIndentComma(b)
|
b = encodeIndentComma(b)
|
||||||
code = code.next
|
code = code.next
|
||||||
}
|
}
|
||||||
|
@ -5908,10 +5902,8 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
|
||||||
case opStructFieldStringTagString:
|
case opStructFieldStringTagString:
|
||||||
ptr := load(ctxptr, code.headIdx)
|
ptr := load(ctxptr, code.headIdx)
|
||||||
b = e.encodeKey(b, code)
|
b = e.encodeKey(b, code)
|
||||||
var buf bytes.Buffer
|
s := e.ptrToString(ptr + code.offset)
|
||||||
enc := NewEncoder(&buf)
|
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
|
||||||
enc.buf = enc.encodeString(enc.buf, e.ptrToString(ptr+code.offset))
|
|
||||||
b = e.encodeString(b, string(enc.buf))
|
|
||||||
code = code.next
|
code = code.next
|
||||||
case opStructFieldStringTagBool:
|
case opStructFieldStringTagBool:
|
||||||
ptr := load(ctxptr, code.headIdx)
|
ptr := load(ctxptr, code.headIdx)
|
||||||
|
@ -6065,12 +6057,9 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
|
||||||
b = e.encodeIndent(b, code.indent)
|
b = e.encodeIndent(b, code.indent)
|
||||||
b = e.encodeKey(b, code)
|
b = e.encodeKey(b, code)
|
||||||
b = append(b, ' ')
|
b = append(b, ' ')
|
||||||
var buf bytes.Buffer
|
s := e.ptrToString(ptr + code.offset)
|
||||||
enc := NewEncoder(&buf)
|
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
|
||||||
enc.buf = enc.encodeString(enc.buf, e.ptrToString(ptr+code.offset))
|
|
||||||
b = e.encodeString(b, string(enc.buf))
|
|
||||||
b = encodeIndentComma(b)
|
b = encodeIndentComma(b)
|
||||||
enc.release()
|
|
||||||
code = code.next
|
code = code.next
|
||||||
case opStructFieldStringTagBoolIndent:
|
case opStructFieldStringTagBoolIndent:
|
||||||
ptr := load(ctxptr, code.headIdx)
|
ptr := load(ctxptr, code.headIdx)
|
||||||
|
|
Loading…
Reference in New Issue