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. // 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

View File

@ -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
} }

View File

@ -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,
} }
} }

View File

@ -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)