Improve encoding performance

This commit is contained in:
Masaaki Goshima 2020-12-25 03:53:48 +09:00
parent 26e4f7f455
commit f5daa592fa
4 changed files with 68 additions and 96 deletions

View File

@ -15,16 +15,15 @@ import (
// An Encoder writes JSON values to an output stream.
type Encoder struct {
w io.Writer
ctx *encodeRuntimeContext
buf []byte
enabledIndent bool
enabledHTMLEscape bool
unorderedMap bool
prefix []byte
indentStr []byte
structTypeToCompiledCode map[uintptr]*compiledCode
structTypeToCompiledIndentCode map[uintptr]*compiledCode
w io.Writer
ctx *encodeRuntimeContext
ptr unsafe.Pointer
buf []byte
enabledIndent bool
enabledHTMLEscape bool
unorderedMap bool
prefix []byte
indentStr []byte
}
type compiledCode struct {
@ -73,9 +72,7 @@ func init() {
ptrs: make([]uintptr, 128),
keepRefs: make([]unsafe.Pointer, 0, 8),
},
buf: make([]byte, 0, bufSize),
structTypeToCompiledCode: map[uintptr]*compiledCode{},
structTypeToCompiledIndentCode: map[uintptr]*compiledCode{},
buf: make([]byte, 0, bufSize),
}
},
}
@ -105,20 +102,20 @@ func (e *Encoder) EncodeWithOption(v interface{}, opts ...EncodeOption) error {
return err
}
}
var err error
if e.buf, err = e.encode(v); err != nil {
buf, err := e.encode(v)
if err != nil {
return err
}
if e.enabledIndent {
e.buf = e.buf[:len(e.buf)-2]
buf = buf[:len(buf)-2]
} else {
e.buf = e.buf[:len(e.buf)-1]
buf = buf[:len(buf)-1]
}
e.buf = append(e.buf, '\n')
if _, err := e.w.Write(e.buf); err != nil {
buf = append(buf, '\n')
if _, err := e.w.Write(buf); err != nil {
return err
}
e.buf = e.buf[:0]
e.buf = buf[:0]
return nil
}
@ -148,29 +145,31 @@ func (e *Encoder) release() {
}
func (e *Encoder) reset() {
e.buf = e.buf[:0]
e.enabledHTMLEscape = true
e.enabledIndent = false
e.unorderedMap = false
}
func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) {
var err error
if e.buf, err = e.encode(v); err != nil {
buf, err := e.encode(v)
if err != nil {
return nil, err
}
e.buf = buf
if e.enabledIndent {
copied := make([]byte, len(e.buf)-2)
copy(copied, e.buf)
copied := make([]byte, len(buf)-2)
copy(copied, buf)
return copied, nil
}
copied := make([]byte, len(e.buf)-1)
copy(copied, e.buf)
copied := make([]byte, len(buf)-1)
copy(copied, buf)
return copied, nil
}
func (e *Encoder) encode(v interface{}) ([]byte, error) {
b := e.buf
b := e.buf[:0]
if v == nil {
b = encodeNull(b)
if e.enabledIndent {
@ -202,17 +201,19 @@ func (e *Encoder) encode(v interface{}) ([]byte, error) {
copiedType := *(**rtype)(unsafe.Pointer(&typeptr))
codeIndent, err := e.compileHead(&encodeCompileContext{
typ: copiedType,
root: true,
withIndent: true,
typ: copiedType,
root: true,
withIndent: true,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
})
if err != nil {
return nil, err
}
code, err := e.compileHead(&encodeCompileContext{
typ: copiedType,
root: true,
withIndent: false,
typ: copiedType,
root: true,
withIndent: false,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
})
if err != nil {
return nil, err

View File

@ -1,7 +1,6 @@
package json
import (
"bytes"
"fmt"
"reflect"
"unsafe"
@ -877,14 +876,8 @@ func (e *Encoder) recursiveCode(ctx *encodeCompileContext, jmp *compiledCode) *o
func (e *Encoder) compiledCode(ctx *encodeCompileContext) *opcode {
typ := ctx.typ
typeptr := uintptr(unsafe.Pointer(typ))
if ctx.withIndent {
if compiledCode, exists := e.structTypeToCompiledIndentCode[typeptr]; exists {
return e.recursiveCode(ctx, compiledCode)
}
} else {
if compiledCode, exists := e.structTypeToCompiledCode[typeptr]; exists {
return e.recursiveCode(ctx, compiledCode)
}
if compiledCode, exists := ctx.structTypeToCompiledCode[typeptr]; exists {
return e.recursiveCode(ctx, compiledCode)
}
return nil
}
@ -1140,11 +1133,7 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
typ := ctx.typ
typeptr := uintptr(unsafe.Pointer(typ))
compiled := &compiledCode{}
if ctx.withIndent {
e.structTypeToCompiledIndentCode[typeptr] = compiled
} else {
e.structTypeToCompiledCode[typeptr] = compiled
}
ctx.structTypeToCompiledCode[typeptr] = compiled
// header => code => structField => code => end
// ^ |
// |__________|
@ -1212,12 +1201,7 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
}
}
key := fmt.Sprintf(`"%s":`, 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()
escapedKey := fmt.Sprintf(`%s:`, string(encodeEscapedString([]byte{}, tag.key)))
fieldCode := &opcode{
typ: valueCode.typ,
displayIdx: fieldOpcodeIndex,
@ -1294,11 +1278,7 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
ret := (*opcode)(unsafe.Pointer(head))
compiled.code = ret
if ctx.withIndent {
delete(e.structTypeToCompiledIndentCode, typeptr)
} else {
delete(e.structTypeToCompiledCode, typeptr)
}
delete(ctx.structTypeToCompiledCode, typeptr)
return ret, nil
}

View File

@ -5,25 +5,27 @@ import (
)
type encodeCompileContext struct {
typ *rtype
withIndent bool
root bool
opcodeIndex int
ptrIndex int
indent int
typ *rtype
withIndent bool
root bool
opcodeIndex int
ptrIndex int
indent int
structTypeToCompiledCode map[uintptr]*compiledCode
parent *encodeCompileContext
}
func (c *encodeCompileContext) context() *encodeCompileContext {
return &encodeCompileContext{
typ: c.typ,
withIndent: c.withIndent,
root: c.root,
opcodeIndex: c.opcodeIndex,
ptrIndex: c.ptrIndex,
indent: c.indent,
parent: c,
typ: c.typ,
withIndent: c.withIndent,
root: c.root,
opcodeIndex: c.opcodeIndex,
ptrIndex: c.ptrIndex,
indent: c.indent,
structTypeToCompiledCode: c.structTypeToCompiledCode,
parent: c,
}
}

View File

@ -317,10 +317,11 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
}
}
c, err := e.compileHead(&encodeCompileContext{
typ: header.typ,
root: code.root,
withIndent: e.enabledIndent,
indent: code.indent,
typ: header.typ,
root: code.root,
withIndent: e.enabledIndent,
indent: code.indent,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
})
if err != nil {
return nil, err
@ -4157,17 +4158,13 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
} else {
b = append(b, '{')
b = e.encodeKey(b, code)
var buf bytes.Buffer
enc := NewEncoder(&buf)
s := e.ptrToString(ptr + code.offset)
if e.enabledHTMLEscape {
enc.buf = encodeEscapedString(enc.buf, s)
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
} 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)
enc.release()
code = code.next
}
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.encodeKey(b, code)
b = append(b, ' ')
var buf bytes.Buffer
enc := NewEncoder(&buf)
s := e.ptrToString(ptr + code.offset)
if e.enabledHTMLEscape {
enc.buf = encodeEscapedString(enc.buf, s)
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
} 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)
code = code.next
}
@ -5908,10 +5902,8 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
case opStructFieldStringTagString:
ptr := load(ctxptr, code.headIdx)
b = e.encodeKey(b, code)
var buf bytes.Buffer
enc := NewEncoder(&buf)
enc.buf = enc.encodeString(enc.buf, e.ptrToString(ptr+code.offset))
b = e.encodeString(b, string(enc.buf))
s := e.ptrToString(ptr + code.offset)
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
code = code.next
case opStructFieldStringTagBool:
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.encodeKey(b, code)
b = append(b, ' ')
var buf bytes.Buffer
enc := NewEncoder(&buf)
enc.buf = enc.encodeString(enc.buf, e.ptrToString(ptr+code.offset))
b = e.encodeString(b, string(enc.buf))
s := e.ptrToString(ptr + code.offset)
b = e.encodeString(b, string(encodeEscapedString([]byte{}, s)))
b = encodeIndentComma(b)
enc.release()
code = code.next
case opStructFieldStringTagBoolIndent:
ptr := load(ctxptr, code.headIdx)