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

View File

@ -312,6 +312,17 @@ func encodeIndentComma(b []byte) []byte {
return append(b, ',', '\n') 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 { func encodeByteSlice(b []byte, src []byte) []byte {
encodedLen := base64.StdEncoding.EncodedLen(len(src)) encodedLen := base64.StdEncoding.EncodedLen(len(src))
b = append(b, '"') b = append(b, '"')

View File

@ -3,6 +3,7 @@ package json
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
"unsafe" "unsafe"
) )
@ -27,13 +28,64 @@ func (e *Encoder) compileHead(ctx *encodeCompileContext) (*opcode, error) {
if typ.Kind() == reflect.Map { if typ.Kind() == reflect.Map {
return e.compileMap(ctx.withType(typ), isPtr) return e.compileMap(ctx.withType(typ), isPtr)
} else if typ.Kind() == reflect.Struct { } 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) { } else if isPtr && typ.Implements(marshalTextType) {
typ = orgType typ = orgType
} else if isPtr && typ.Implements(marshalJSONType) { } else if isPtr && typ.Implements(marshalJSONType) {
typ = orgType 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 { func (e *Encoder) implementsMarshaler(typ *rtype) bool {

View File

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