From f5daa592fa8357cd6d992c627fa634e41a43a3e1 Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Fri, 25 Dec 2020 03:53:48 +0900 Subject: [PATCH] Improve encoding performance --- encode.go | 69 ++++++++++++++++++++++++----------------------- encode_compile.go | 30 ++++----------------- encode_context.go | 28 ++++++++++--------- encode_vm.go | 37 +++++++++---------------- 4 files changed, 68 insertions(+), 96 deletions(-) diff --git a/encode.go b/encode.go index 55fdf41..8618fb7 100644 --- a/encode.go +++ b/encode.go @@ -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 diff --git a/encode_compile.go b/encode_compile.go index 9b4480f..fe70c08 100644 --- a/encode_compile.go +++ b/encode_compile.go @@ -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 } diff --git a/encode_context.go b/encode_context.go index cdeefb4..c3a420d 100644 --- a/encode_context.go +++ b/encode_context.go @@ -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, } } diff --git a/encode_vm.go b/encode_vm.go index 1761d2b..f396e33 100644 --- a/encode_vm.go +++ b/encode_vm.go @@ -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)