Optimize json.Compact and json.Indent

This commit is contained in:
Masaaki Goshima 2021-04-20 20:12:32 +09:00
parent e66e5606b5
commit 7ae14cd24c
9 changed files with 142 additions and 114 deletions

View File

@ -2,7 +2,6 @@ package json
import ( import (
"io" "io"
"sync"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/encoder"
@ -22,10 +21,6 @@ type Encoder struct {
indentStr string indentStr string
} }
const (
bufSize = 1024
)
type EncodeOption int type EncodeOption int
const ( const (
@ -35,26 +30,6 @@ const (
EncodeOptionDebug EncodeOptionDebug
) )
var (
encRuntimeContextPool = sync.Pool{
New: func() interface{} {
return &encoder.RuntimeContext{
Buf: make([]byte, 0, bufSize),
Ptrs: make([]uintptr, 128),
KeepRefs: make([]unsafe.Pointer, 0, 8),
}
},
}
)
func takeEncodeRuntimeContext() *encoder.RuntimeContext {
return encRuntimeContextPool.Get().(*encoder.RuntimeContext)
}
func releaseEncodeRuntimeContext(ctx *encoder.RuntimeContext) {
encRuntimeContextPool.Put(ctx)
}
// NewEncoder returns a new encoder that writes to w. // NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder { func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w, enabledHTMLEscape: true} return &Encoder{w: w, enabledHTMLEscape: true}
@ -69,11 +44,11 @@ func (e *Encoder) Encode(v interface{}) error {
// EncodeWithOption call Encode with EncodeOption. // EncodeWithOption call Encode with EncodeOption.
func (e *Encoder) EncodeWithOption(v interface{}, optFuncs ...EncodeOptionFunc) error { func (e *Encoder) EncodeWithOption(v interface{}, optFuncs ...EncodeOptionFunc) error {
ctx := takeEncodeRuntimeContext() ctx := encoder.TakeRuntimeContext()
err := e.encodeWithOption(ctx, v, optFuncs...) err := e.encodeWithOption(ctx, v, optFuncs...)
releaseEncodeRuntimeContext(ctx) encoder.ReleaseRuntimeContext(ctx)
return err return err
} }
@ -130,11 +105,11 @@ func (e *Encoder) SetIndent(prefix, indent string) {
} }
func marshal(v interface{}, opt EncodeOption) ([]byte, error) { func marshal(v interface{}, opt EncodeOption) ([]byte, error) {
ctx := takeEncodeRuntimeContext() ctx := encoder.TakeRuntimeContext()
buf, err := encode(ctx, v, opt|EncodeOptionHTMLEscape) buf, err := encode(ctx, v, opt|EncodeOptionHTMLEscape)
if err != nil { if err != nil {
releaseEncodeRuntimeContext(ctx) encoder.ReleaseRuntimeContext(ctx)
return nil, err return nil, err
} }
@ -146,16 +121,16 @@ func marshal(v interface{}, opt EncodeOption) ([]byte, error) {
copied := make([]byte, len(buf)) copied := make([]byte, len(buf))
copy(copied, buf) copy(copied, buf)
releaseEncodeRuntimeContext(ctx) encoder.ReleaseRuntimeContext(ctx)
return copied, nil return copied, nil
} }
func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) { func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) {
ctx := takeEncodeRuntimeContext() ctx := encoder.TakeRuntimeContext()
buf, err := encodeNoEscape(ctx, v, opt|EncodeOptionHTMLEscape) buf, err := encodeNoEscape(ctx, v, opt|EncodeOptionHTMLEscape)
if err != nil { if err != nil {
releaseEncodeRuntimeContext(ctx) encoder.ReleaseRuntimeContext(ctx)
return nil, err return nil, err
} }
@ -167,16 +142,16 @@ func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) {
copied := make([]byte, len(buf)) copied := make([]byte, len(buf))
copy(copied, buf) copy(copied, buf)
releaseEncodeRuntimeContext(ctx) encoder.ReleaseRuntimeContext(ctx)
return copied, nil return copied, nil
} }
func marshalIndent(v interface{}, prefix, indent string, opt EncodeOption) ([]byte, error) { func marshalIndent(v interface{}, prefix, indent string, opt EncodeOption) ([]byte, error) {
ctx := takeEncodeRuntimeContext() ctx := encoder.TakeRuntimeContext()
buf, err := encodeIndent(ctx, v, prefix, indent, opt|EncodeOptionHTMLEscape) buf, err := encodeIndent(ctx, v, prefix, indent, opt|EncodeOptionHTMLEscape)
if err != nil { if err != nil {
releaseEncodeRuntimeContext(ctx) encoder.ReleaseRuntimeContext(ctx)
return nil, err return nil, err
} }
@ -184,7 +159,7 @@ func marshalIndent(v interface{}, prefix, indent string, opt EncodeOption) ([]by
copied := make([]byte, len(buf)) copied := make([]byte, len(buf))
copy(copied, buf) copy(copied, buf)
releaseEncodeRuntimeContext(ctx) encoder.ReleaseRuntimeContext(ctx)
return copied, nil return copied, nil
} }

View File

@ -30,9 +30,22 @@ func Compact(buf *bytes.Buffer, src []byte, escape bool) error {
} }
buf.Grow(len(src)) buf.Grow(len(src))
dst := buf.Bytes() dst := buf.Bytes()
newSrc := make([]byte, len(src)+1) // append nul byte to the end
copy(newSrc, src) ctx := TakeRuntimeContext()
dst, err := compact(dst, newSrc, escape) ctxBuf := ctx.Buf[:0]
ctxBuf = append(append(ctxBuf, src...), nul)
ctx.Buf = ctxBuf
if err := compactAndWrite(buf, dst, ctxBuf, escape); err != nil {
ReleaseRuntimeContext(ctx)
return err
}
ReleaseRuntimeContext(ctx)
return nil
}
func compactAndWrite(buf *bytes.Buffer, dst []byte, src []byte, escape bool) error {
dst, err := compact(dst, src, escape)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,6 +1,9 @@
package encoder package encoder
import ( import (
"sync"
"unsafe"
"github.com/goccy/go-json/internal/runtime" "github.com/goccy/go-json/internal/runtime"
) )
@ -80,3 +83,53 @@ func (c *compileContext) decPtrIndex() {
c.parent.decPtrIndex() c.parent.decPtrIndex()
} }
} }
const (
bufSize = 1024
)
var (
runtimeContextPool = sync.Pool{
New: func() interface{} {
return &RuntimeContext{
Buf: make([]byte, 0, bufSize),
Ptrs: make([]uintptr, 128),
KeepRefs: make([]unsafe.Pointer, 0, 8),
}
},
}
)
type RuntimeContext struct {
Buf []byte
MarshalBuf []byte
Ptrs []uintptr
KeepRefs []unsafe.Pointer
SeenPtr []uintptr
BaseIndent int
Prefix []byte
IndentStr []byte
}
func (c *RuntimeContext) Init(p uintptr, codelen int) {
if len(c.Ptrs) < codelen {
c.Ptrs = make([]uintptr, codelen)
}
c.Ptrs[0] = p
c.KeepRefs = c.KeepRefs[:0]
c.SeenPtr = c.SeenPtr[:0]
c.BaseIndent = 0
}
func (c *RuntimeContext) Ptr() uintptr {
header := (*runtime.SliceHeader)(unsafe.Pointer(&c.Ptrs))
return uintptr(header.Data)
}
func TakeRuntimeContext() *RuntimeContext {
return runtimeContextPool.Get().(*RuntimeContext)
}
func ReleaseRuntimeContext(ctx *RuntimeContext) {
runtimeContextPool.Put(ctx)
}

View File

@ -304,32 +304,6 @@ func MapIterNext(it unsafe.Pointer)
//go:noescape //go:noescape
func MapLen(m unsafe.Pointer) int func MapLen(m unsafe.Pointer) int
type RuntimeContext struct {
Buf []byte
MarshalBuf []byte
Ptrs []uintptr
KeepRefs []unsafe.Pointer
SeenPtr []uintptr
BaseIndent int
Prefix []byte
IndentStr []byte
}
func (c *RuntimeContext) Init(p uintptr, codelen int) {
if len(c.Ptrs) < codelen {
c.Ptrs = make([]uintptr, codelen)
}
c.Ptrs[0] = p
c.KeepRefs = c.KeepRefs[:0]
c.SeenPtr = c.SeenPtr[:0]
c.BaseIndent = 0
}
func (c *RuntimeContext) Ptr() uintptr {
header := (*runtime.SliceHeader)(unsafe.Pointer(&c.Ptrs))
return uintptr(header.Data)
}
func AppendByteSlice(b []byte, src []byte) []byte { func AppendByteSlice(b []byte, src []byte) []byte {
if src == nil { if src == nil {
return append(b, `null`...) return append(b, `null`...)
@ -551,13 +525,20 @@ func AppendStructEnd(b []byte) []byte {
func AppendStructEndIndent(ctx *RuntimeContext, b []byte, indent int) []byte { func AppendStructEndIndent(ctx *RuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n') b = append(b, '\n')
b = append(b, ctx.Prefix...) b = append(b, ctx.Prefix...)
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...) indentNum := ctx.BaseIndent + indent
for i := 0; i < indentNum; i++ {
b = append(b, ctx.IndentStr...)
}
return append(b, '}', ',', '\n') return append(b, '}', ',', '\n')
} }
func AppendIndent(ctx *RuntimeContext, b []byte, indent int) []byte { func AppendIndent(ctx *RuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.Prefix...) b = append(b, ctx.Prefix...)
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...) indentNum := ctx.BaseIndent + indent
for i := 0; i < indentNum; i++ {
b = append(b, ctx.IndentStr...)
}
return b
} }
func IsNilForMarshaler(v interface{}) bool { func IsNilForMarshaler(v interface{}) bool {

View File

@ -7,22 +7,44 @@ import (
"github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/errors"
) )
func takeIndentSrcRuntimeContext(src []byte) (*RuntimeContext, []byte) {
ctx := TakeRuntimeContext()
buf := ctx.Buf[:0]
buf = append(append(buf, src...), nul)
ctx.Buf = buf
return ctx, buf
}
func Indent(buf *bytes.Buffer, src []byte, prefix, indentStr string) error { func Indent(buf *bytes.Buffer, src []byte, prefix, indentStr string) error {
if len(src) == 0 { if len(src) == 0 {
return errors.ErrUnexpectedEndOfJSON("", 0) return errors.ErrUnexpectedEndOfJSON("", 0)
} }
buf.Grow(len(src))
dst := buf.Bytes() srcCtx, srcBuf := takeIndentSrcRuntimeContext(src)
newSrc := make([]byte, len(src)+1) // append nul byte to the end dstCtx := TakeRuntimeContext()
copy(newSrc, src) dst := dstCtx.Buf[:0]
dst, err := doIndent(dst, newSrc, prefix, indentStr, false)
dst, err := indentAndWrite(buf, dst, srcBuf, prefix, indentStr)
if err != nil { if err != nil {
ReleaseRuntimeContext(srcCtx)
ReleaseRuntimeContext(dstCtx)
return err return err
} }
dstCtx.Buf = dst
ReleaseRuntimeContext(srcCtx)
ReleaseRuntimeContext(dstCtx)
return nil
}
func indentAndWrite(buf *bytes.Buffer, dst []byte, src []byte, prefix, indentStr string) ([]byte, error) {
dst, err := doIndent(dst, src, prefix, indentStr, false)
if err != nil {
return nil, err
}
if _, err := buf.Write(dst); err != nil { if _, err := buf.Write(dst); err != nil {
return err return nil, err
} }
return nil return dst, nil
} }
func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) { func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) {
@ -94,7 +116,10 @@ func indentObject(
indentNum++ indentNum++
var err error var err error
for { for {
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...) dst = append(append(dst, '\n'), prefix...)
for i := 0; i < indentNum; i++ {
dst = append(dst, indentBytes...)
}
cursor = skipWhiteSpace(src, cursor) cursor = skipWhiteSpace(src, cursor)
dst, cursor, err = compactString(dst, src, cursor, escape) dst, cursor, err = compactString(dst, src, cursor, escape)
if err != nil { if err != nil {
@ -115,7 +140,10 @@ func indentObject(
cursor = skipWhiteSpace(src, cursor) cursor = skipWhiteSpace(src, cursor)
switch src[cursor] { switch src[cursor] {
case '}': case '}':
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...) dst = append(append(dst, '\n'), prefix...)
for i := 0; i < indentNum-1; i++ {
dst = append(dst, indentBytes...)
}
dst = append(dst, '}') dst = append(dst, '}')
cursor++ cursor++
return dst, cursor, nil return dst, cursor, nil
@ -152,7 +180,10 @@ func indentArray(
indentNum++ indentNum++
var err error var err error
for { for {
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...) dst = append(append(dst, '\n'), prefix...)
for i := 0; i < indentNum; i++ {
dst = append(dst, indentBytes...)
}
dst, cursor, err = indentValue(dst, src, indentNum, cursor, prefix, indentBytes, escape) dst, cursor, err = indentValue(dst, src, indentNum, cursor, prefix, indentBytes, escape)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@ -160,7 +191,10 @@ func indentArray(
cursor = skipWhiteSpace(src, cursor) cursor = skipWhiteSpace(src, cursor)
switch src[cursor] { switch src[cursor] {
case ']': case ']':
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...) dst = append(append(dst, '\n'), prefix...)
for i := 0; i < indentNum-1; i++ {
dst = append(dst, indentBytes...)
}
dst = append(dst, ']') dst = append(dst, ']')
cursor++ cursor++
return dst, cursor, nil return dst, cursor, nil

View File

@ -1,7 +1,6 @@
package vm_escaped_indent package vm_escaped_indent
import ( import (
"bytes"
"encoding/json" "encoding/json"
"unsafe" "unsafe"
@ -76,15 +75,3 @@ func appendNull(b []byte) []byte {
func appendComma(b []byte) []byte { func appendComma(b []byte) []byte {
return append(b, ',', '\n') return append(b, ',', '\n')
} }
func appendStructEnd(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, ctx.Prefix...)
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
return append(b, '}', ',', '\n')
}
func appendIndent(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.Prefix...)
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
}

View File

@ -1,7 +1,6 @@
package vm_escaped_indent package vm_escaped_indent
import ( import (
"bytes"
"fmt" "fmt"
"math" "math"
"sort" "sort"
@ -29,6 +28,8 @@ var (
appendNumber = encoder.AppendNumber appendNumber = encoder.AppendNumber
appendMarshalJSON = encoder.AppendMarshalJSONIndent appendMarshalJSON = encoder.AppendMarshalJSONIndent
appendMarshalText = encoder.AppendMarshalTextIndent appendMarshalText = encoder.AppendMarshalTextIndent
appendStructEnd = encoder.AppendStructEndIndent
appendIndent = encoder.AppendIndent
errUnsupportedValue = encoder.ErrUnsupportedValue errUnsupportedValue = encoder.ErrUnsupportedValue
errUnsupportedFloat = encoder.ErrUnsupportedFloat errUnsupportedFloat = encoder.ErrUnsupportedFloat
mapiterinit = encoder.MapIterInit mapiterinit = encoder.MapIterInit
@ -518,8 +519,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
sort.Sort(mapCtx.Slice) sort.Sort(mapCtx.Slice)
buf := mapCtx.Buf buf := mapCtx.Buf
for _, item := range mapCtx.Slice.Items { for _, item := range mapCtx.Slice.Items {
buf = append(buf, ctx.Prefix...) buf = appendIndent(ctx, buf, code.Indent+1)
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent+1)...)
buf = append(buf, item.Key...) buf = append(buf, item.Key...)
buf[len(buf)-2] = ':' buf[len(buf)-2] = ':'
buf[len(buf)-1] = ' ' buf[len(buf)-1] = ' '
@ -527,8 +527,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
} }
buf = buf[:len(buf)-2] buf = buf[:len(buf)-2]
buf = append(buf, '\n') buf = append(buf, '\n')
buf = append(buf, ctx.Prefix...) buf = appendIndent(ctx, buf, code.Indent)
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent)...)
buf = append(buf, '}', ',', '\n') buf = append(buf, '}', ',', '\n')
b = b[:pos[0]] b = b[:pos[0]]

View File

@ -1,7 +1,6 @@
package vm_indent package vm_indent
import ( import (
"bytes"
"encoding/json" "encoding/json"
"unsafe" "unsafe"
@ -76,15 +75,3 @@ func appendNull(b []byte) []byte {
func appendComma(b []byte) []byte { func appendComma(b []byte) []byte {
return append(b, ',', '\n') return append(b, ',', '\n')
} }
func appendStructEnd(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, ctx.Prefix...)
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
return append(b, '}', ',', '\n')
}
func appendIndent(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.Prefix...)
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
}

View File

@ -1,7 +1,6 @@
package vm_indent package vm_indent
import ( import (
"bytes"
"fmt" "fmt"
"math" "math"
"sort" "sort"
@ -29,6 +28,8 @@ var (
appendNumber = encoder.AppendNumber appendNumber = encoder.AppendNumber
appendMarshalJSON = encoder.AppendMarshalJSONIndent appendMarshalJSON = encoder.AppendMarshalJSONIndent
appendMarshalText = encoder.AppendMarshalTextIndent appendMarshalText = encoder.AppendMarshalTextIndent
appendStructEnd = encoder.AppendStructEndIndent
appendIndent = encoder.AppendIndent
errUnsupportedValue = encoder.ErrUnsupportedValue errUnsupportedValue = encoder.ErrUnsupportedValue
errUnsupportedFloat = encoder.ErrUnsupportedFloat errUnsupportedFloat = encoder.ErrUnsupportedFloat
mapiterinit = encoder.MapIterInit mapiterinit = encoder.MapIterInit
@ -518,8 +519,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
sort.Sort(mapCtx.Slice) sort.Sort(mapCtx.Slice)
buf := mapCtx.Buf buf := mapCtx.Buf
for _, item := range mapCtx.Slice.Items { for _, item := range mapCtx.Slice.Items {
buf = append(buf, ctx.Prefix...) buf = appendIndent(ctx, buf, code.Indent+1)
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent+1)...)
buf = append(buf, item.Key...) buf = append(buf, item.Key...)
buf[len(buf)-2] = ':' buf[len(buf)-2] = ':'
buf[len(buf)-1] = ' ' buf[len(buf)-1] = ' '
@ -527,8 +527,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
} }
buf = buf[:len(buf)-2] buf = buf[:len(buf)-2]
buf = append(buf, '\n') buf = append(buf, '\n')
buf = append(buf, ctx.Prefix...) buf = appendIndent(ctx, buf, code.Indent)
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent)...)
buf = append(buf, '}', ',', '\n') buf = append(buf, '}', ',', '\n')
b = b[:pos[0]] b = b[:pos[0]]