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

@ -17,14 +17,13 @@ import (
type Encoder struct {
w io.Writer
ctx *encodeRuntimeContext
ptr unsafe.Pointer
buf []byte
enabledIndent bool
enabledHTMLEscape bool
unorderedMap bool
prefix []byte
indentStr []byte
structTypeToCompiledCode map[uintptr]*compiledCode
structTypeToCompiledIndentCode map[uintptr]*compiledCode
}
type compiledCode struct {
@ -74,8 +73,6 @@ func init() {
keepRefs: make([]unsafe.Pointer, 0, 8),
},
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
}
}
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 {
@ -205,6 +204,7 @@ func (e *Encoder) encode(v interface{}) ([]byte, error) {
typ: copiedType,
root: true,
withIndent: true,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
})
if err != nil {
return nil, err
@ -213,6 +213,7 @@ func (e *Encoder) encode(v interface{}) ([]byte, error) {
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,15 +876,9 @@ 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 {
if compiledCode, exists := ctx.structTypeToCompiledCode[typeptr]; exists {
return e.recursiveCode(ctx, compiledCode)
}
} else {
if compiledCode, exists := e.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

@ -11,6 +11,7 @@ type encodeCompileContext struct {
opcodeIndex int
ptrIndex int
indent int
structTypeToCompiledCode map[uintptr]*compiledCode
parent *encodeCompileContext
}
@ -23,6 +24,7 @@ func (c *encodeCompileContext) context() *encodeCompileContext {
opcodeIndex: c.opcodeIndex,
ptrIndex: c.ptrIndex,
indent: c.indent,
structTypeToCompiledCode: c.structTypeToCompiledCode,
parent: c,
}
}

View File

@ -321,6 +321,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte
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)