Merge pull request #77 from goccy/feature/optimize-struct-end

Optimize StructEnd operation
This commit is contained in:
Masaaki Goshima 2020-12-30 16:58:25 +09:00 committed by GitHub
commit cc45937ab1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 11527 additions and 110594 deletions

View File

@ -22,10 +22,51 @@ type opType struct {
HeadToOmitEmptyHead func() string
HeadToStringTagHead func() string
PtrHeadToHead func() string
FieldToEnd func() string
FieldToOmitEmptyField func() string
FieldToStringTagField func() string
}
func (t opType) IsEscaped() bool {
return t.Op != t.Escaped()
}
func (t opType) IsHeadToPtrHead() bool {
return t.Op != t.HeadToPtrHead()
}
func (t opType) IsHeadToNPtrHead() bool {
return t.Op != t.HeadToNPtrHead()
}
func (t opType) IsHeadToAnonymousHead() bool {
return t.Op != t.HeadToAnonymousHead()
}
func (t opType) IsHeadToOmitEmptyHead() bool {
return t.Op != t.HeadToOmitEmptyHead()
}
func (t opType) IsHeadToStringTagHead() bool {
return t.Op != t.HeadToStringTagHead()
}
func (t opType) IsPtrHeadToHead() bool {
return t.Op != t.PtrHeadToHead()
}
func (t opType) IsFieldToEnd() bool {
return t.Op != t.FieldToEnd()
}
func (t opType) IsFieldToOmitEmptyField() bool {
return t.Op != t.FieldToOmitEmptyField()
}
func (t opType) IsFieldToStringTagField() bool {
return t.Op != t.FieldToStringTagField()
}
func createOpType(op, code string) opType {
return opType{
Op: op,
@ -38,6 +79,7 @@ func createOpType(op, code string) opType {
HeadToOmitEmptyHead: func() string { return op },
HeadToStringTagHead: func() string { return op },
PtrHeadToHead: func() string { return op },
FieldToEnd: func() string { return op },
FieldToOmitEmptyField: func() string { return op },
FieldToStringTagField: func() string { return op },
}
@ -64,8 +106,12 @@ const (
)
func (t opType) String() string {
if int(t) >= {{ .OpLen }} {
return t.toNotIndent().String() + "Indent"
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
case op{{ $type.Op }}:
return "{{ $type.Op }}"
{{- end }}
@ -74,8 +120,12 @@ func (t opType) String() string {
}
func (t opType) codeType() codeType {
if int(t) >= {{ .OpLen }} {
return t.toNotIndent().codeType()
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
case op{{ $type.Op }}:
return code{{ $type.Code }}
{{- end }}
@ -83,101 +133,175 @@ func (t opType) codeType() codeType {
return codeOp
}
func (t opType) toIndent() opType {
switch t {
{{- range $type := .OpTypes }}
case op{{ $type.Op }}:
return op{{ call $type.Indent }}
{{- end }}
func (t opType) toNotIndent() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t) - {{ .OpLen }})
}
return t
}
func (t opType) toIndent() opType {
if int(t) >= {{ .OpLen }} {
return t
}
return opType(int(t) + {{ .OpLen }})
}
func (t opType) toEscaped() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().toEscaped()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsEscaped }}
case op{{ $type.Op }}:
return op{{ call $type.Escaped }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) headToPtrHead() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().headToPtrHead()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsHeadToPtrHead }}
case op{{ $type.Op }}:
return op{{ call $type.HeadToPtrHead }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) headToNPtrHead() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().headToNPtrHead()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsHeadToNPtrHead }}
case op{{ $type.Op }}:
return op{{ call $type.HeadToNPtrHead }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) headToAnonymousHead() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().headToAnonymousHead()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsHeadToAnonymousHead }}
case op{{ $type.Op }}:
return op{{ call $type.HeadToAnonymousHead }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) headToOmitEmptyHead() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().headToOmitEmptyHead()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsHeadToOmitEmptyHead }}
case op{{ $type.Op }}:
return op{{ call $type.HeadToOmitEmptyHead }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) headToStringTagHead() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().headToStringTagHead()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsHeadToStringTagHead }}
case op{{ $type.Op }}:
return op{{ call $type.HeadToStringTagHead }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) ptrHeadToHead() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().ptrHeadToHead()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsPtrHeadToHead }}
case op{{ $type.Op }}:
return op{{ call $type.PtrHeadToHead }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) fieldToEnd() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().fieldToEnd()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsFieldToEnd }}
case op{{ $type.Op }}:
return op{{ call $type.FieldToEnd }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) fieldToOmitEmptyField() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().fieldToOmitEmptyField()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsFieldToOmitEmptyField }}
case op{{ $type.Op }}:
return op{{ call $type.FieldToOmitEmptyField }}
{{- end }}
{{- end }}
}
return t
}
func (t opType) fieldToStringTagField() opType {
if int(t) >= {{ .OpLen }} {
return opType(int(t.toNotIndent().fieldToStringTagField()) + {{ .OpLen }})
}
switch t {
{{- range $type := .OpTypes }}
{{- range $type := .OpNotIndentTypes }}
{{- if $type.IsFieldToStringTagField }}
case op{{ $type.Op }}:
return op{{ call $type.FieldToStringTagField }}
{{- end }}
{{- end }}
}
return t
@ -199,6 +323,7 @@ func (t opType) fieldToStringTagField() opType {
"MapEnd",
"StructFieldRecursive",
"StructField",
"StructEnd",
}
primitiveTypes := []string{
"int", "int8", "int16", "int32", "int64",
@ -238,8 +363,7 @@ func (t opType) fieldToStringTagField() opType {
createOpType("MapValue", "MapValue"),
createOpType("MapEnd", "Op"),
createOpType("StructFieldRecursiveEnd", "Op"),
createOpType("StructEnd", "StructField"),
createOpType("StructAnonymousEnd", "StructField"),
createOpType("StructAnonymousEnd", "StructEnd"),
}
for _, typ := range primitiveTypesUpper {
typ := typ
@ -348,6 +472,7 @@ func (t opType) fieldToStringTagField() opType {
typ,
)
},
FieldToEnd: func() string { return op },
FieldToOmitEmptyField: func() string { return op },
FieldToStringTagField: func() string { return op },
})
@ -394,6 +519,18 @@ func (t opType) fieldToStringTagField() opType {
HeadToOmitEmptyHead: func() string { return op },
HeadToStringTagHead: func() string { return op },
PtrHeadToHead: func() string { return op },
FieldToEnd: func() string {
switch typ {
case "", "Array", "Map", "MapLoad", "Slice", "Struct", "Recursive":
return op
}
return fmt.Sprintf(
"Struct%sEnd%s%s",
escapedOrNot,
opt,
typ,
)
},
FieldToOmitEmptyField: func() string {
return fmt.Sprintf(
"Struct%sFieldOmitEmpty%s",
@ -412,6 +549,51 @@ func (t opType) fieldToStringTagField() opType {
}
}
}
for _, escapedOrNot := range []string{"", "Escaped"} {
for _, opt := range []string{"", "OmitEmpty", "StringTag"} {
for _, typ := range append(primitiveTypesUpper, "") {
escapedOrNot := escapedOrNot
opt := opt
typ := typ
op := fmt.Sprintf(
"Struct%sEnd%s%s",
escapedOrNot,
opt,
typ,
)
opTypes = append(opTypes, opType{
Op: op,
Code: "StructEnd",
Indent: func() string { return fmt.Sprintf("%sIndent", op) },
Escaped: func() string {
switch typ {
case "String", "StringPtr", "StringNPtr":
return fmt.Sprintf(
"StructEscapedEnd%sEscaped%s",
opt,
typ,
)
}
return fmt.Sprintf(
"StructEscapedEnd%s%s",
opt,
typ,
)
},
HeadToPtrHead: func() string { return op },
HeadToNPtrHead: func() string { return op },
HeadToAnonymousHead: func() string { return op },
HeadToOmitEmptyHead: func() string { return op },
HeadToStringTagHead: func() string { return op },
PtrHeadToHead: func() string { return op },
FieldToEnd: func() string { return op },
FieldToOmitEmptyField: func() string { return op },
FieldToStringTagField: func() string { return op },
})
}
}
}
indentOpTypes := []opType{}
for _, typ := range opTypes {
typ := typ
@ -444,15 +626,24 @@ func (t opType) fieldToStringTagField() opType {
FieldToStringTagField: func() string {
return fmt.Sprintf("%sIndent", typ.FieldToStringTagField())
},
FieldToEnd: func() string {
return fmt.Sprintf("%sIndent", typ.FieldToEnd())
},
})
}
var b bytes.Buffer
if err := tmpl.Execute(&b, struct {
CodeTypes []string
OpTypes []opType
OpNotIndentTypes []opType
OpLen int
OpIndentLen int
}{
CodeTypes: codeTypes,
OpTypes: append(opTypes, indentOpTypes...),
OpNotIndentTypes: opTypes,
OpLen: len(opTypes),
OpIndentLen: len(indentOpTypes),
}); err != nil {
return err
}

View File

@ -312,6 +312,17 @@ func encodeIndentComma(b []byte) []byte {
return append(b, ',', '\n')
}
func appendStructEnd(b []byte) []byte {
return append(b, '}', ',')
}
func (e *Encoder) appendStructEndIndent(b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, e.prefix...)
b = append(b, bytes.Repeat(e.indentStr, indent)...)
return append(b, '}', ',', '\n')
}
func encodeByteSlice(b []byte, src []byte) []byte {
encodedLen := base64.StdEncoding.EncodedLen(len(src))
b = append(b, '"')

View File

@ -3,6 +3,7 @@ package json
import (
"fmt"
"reflect"
"strings"
"unsafe"
)
@ -27,13 +28,64 @@ func (e *Encoder) compileHead(ctx *encodeCompileContext) (*opcode, error) {
if typ.Kind() == reflect.Map {
return e.compileMap(ctx.withType(typ), isPtr)
} else if typ.Kind() == reflect.Struct {
return e.compileStruct(ctx.withType(typ), isPtr)
code, err := e.compileStruct(ctx.withType(typ), isPtr)
if err != nil {
return nil, err
}
e.optimizeStructEnd(code)
return code, nil
} else if isPtr && typ.Implements(marshalTextType) {
typ = orgType
} else if isPtr && typ.Implements(marshalJSONType) {
typ = orgType
}
return e.compile(ctx.withType(typ))
code, err := e.compile(ctx.withType(typ))
if err != nil {
return nil, err
}
e.optimizeStructEnd(code)
return code, nil
}
func (e *Encoder) optimizeStructEnd(c *opcode) {
for code := c; code.op != opEnd; {
if code.op == opStructFieldRecursive {
// ignore if exists recursive operation
return
}
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
code = code.end
default:
code = code.next
}
}
for code := c; code.op != opEnd; {
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
code = code.end
case codeStructEnd:
switch code.op {
case opStructEnd:
prev := code.prevField
if strings.Contains(prev.op.String(), "Head") {
// not exists field
code = code.next
break
}
if prev.op != prev.op.fieldToEnd() {
prev.op = prev.op.fieldToEnd()
prev.next = code.next
}
code = code.next
default:
code = code.next
}
default:
code = code.next
}
}
}
func (e *Encoder) implementsMarshaler(typ *rtype) bool {

View File

@ -324,6 +324,9 @@ func (c *opcode) dump() string {
case codeStructField:
codes = append(codes, c.dumpField(code))
code = code.next
case codeStructEnd:
codes = append(codes, c.dumpField(code))
code = code.next
default:
codes = append(codes, fmt.Sprintf(
"[%d]%s%s ([idx:%d])",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff