Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Pretouch recursively for large/deep struct #137

Merged
merged 1 commit into from
Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions decoder/assembler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ type JsonStruct struct {
func TestAssembler_DecodeStruct(t *testing.T) {
var v JsonStruct
s := `{"A": 123, "B": "asdf", "C": {"qwer": 4567}, "D": [1, 2, 3, 4, 5]}`
p, err := make(_Compiler).compile(reflect.TypeOf(v))
p, err := newCompiler().compile(reflect.TypeOf(v))
require.NoError(t, err)
k := new(_Stack)
a := newAssembler(p)
Expand All @@ -693,7 +693,7 @@ type Tx struct {
func TestAssembler_DecodeStruct_SinglePrivateField(t *testing.T) {
var v Tx
s := `{"x": 1}`
p, err := make(_Compiler).compile(reflect.TypeOf(v))
p, err := newCompiler().compile(reflect.TypeOf(v))
require.NoError(t, err)
k := new(_Stack)
a := newAssembler(p)
Expand All @@ -707,7 +707,7 @@ func TestAssembler_DecodeStruct_SinglePrivateField(t *testing.T) {
func TestAssembler_DecodeByteSlice_Bin(t *testing.T) {
var v []byte
s := `"aGVsbG8sIHdvcmxk"`
p, err := make(_Compiler).compile(reflect.TypeOf(v))
p, err := newCompiler().compile(reflect.TypeOf(v))
require.NoError(t, err)
k := new(_Stack)
a := newAssembler(p)
Expand All @@ -721,7 +721,7 @@ func TestAssembler_DecodeByteSlice_Bin(t *testing.T) {
func TestAssembler_DecodeByteSlice_List(t *testing.T) {
var v []byte
s := `[104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100]`
p, err := make(_Compiler).compile(reflect.TypeOf(v))
p, err := newCompiler().compile(reflect.TypeOf(v))
require.NoError(t, err)
k := new(_Stack)
a := newAssembler(p)
Expand Down
82 changes: 51 additions & 31 deletions decoder/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
`github.com/bytedance/sonic/internal/caching`
`github.com/bytedance/sonic/internal/resolver`
`github.com/bytedance/sonic/internal/rt`
`github.com/bytedance/sonic/option`
)

type _Op uint8
Expand Down Expand Up @@ -389,7 +390,7 @@ func (self _Instr) formatStructFields() string {
}

type (
_Program []_Instr
_Program []_Instr
)

func (self _Program) pc() int {
Expand Down Expand Up @@ -474,11 +475,27 @@ func (self _Program) disassemble() string {
return strings.Join(append(ret, "\tend"), "\n")
}

type (
_Compiler map[reflect.Type]bool
)
type _Compiler struct {
opts option.CompileOptions
tab map[reflect.Type]bool
rec map[reflect.Type]bool
}

func newCompiler() *_Compiler {
return &_Compiler {
tab: map[reflect.Type]bool{},
}
}

func (self _Compiler) rescue(ep *error) {
func (self *_Compiler) apply(opts option.CompileOptions) *_Compiler {
self.opts = opts
if self.opts.RecursiveDepth > 0 {
self.rec = map[reflect.Type]bool{}
}
return self
}

func (self *_Compiler) rescue(ep *error) {
if val := recover(); val != nil {
if err, ok := val.(error); ok {
*ep = err
Expand All @@ -488,14 +505,14 @@ func (self _Compiler) rescue(ep *error) {
}
}

func (self _Compiler) compile(vt reflect.Type) (ret _Program, err error) {
func (self *_Compiler) compile(vt reflect.Type) (ret _Program, err error) {
defer self.rescue(&err)
self.compileOne(&ret, 0, vt)
return
}

func (self _Compiler) compileOne(p *_Program, sp int, vt reflect.Type) {
ok := self[vt]
func (self *_Compiler) compileOne(p *_Program, sp int, vt reflect.Type) {
ok := self.tab[vt]
pt := reflect.PtrTo(vt)

/* check for recursive nesting */
Expand Down Expand Up @@ -533,12 +550,12 @@ func (self _Compiler) compileOne(p *_Program, sp int, vt reflect.Type) {

/* enter the recursion */
p.add(_OP_lspace)
self[vt] = true
self.tab[vt] = true
self.compileOps(p, sp, vt)
delete(self, vt)
delete(self.tab, vt)
}

func (self _Compiler) compileOps(p *_Program, sp int, vt reflect.Type) {
func (self *_Compiler) compileOps(p *_Program, sp int, vt reflect.Type) {
switch vt.Kind() {
case reflect.Bool : self.compilePrimitive (p, _OP_bool)
case reflect.Int : self.compilePrimitive (p, _OP_int())
Expand All @@ -565,7 +582,7 @@ func (self _Compiler) compileOps(p *_Program, sp int, vt reflect.Type) {
}
}

func (self _Compiler) compileMap(p *_Program, sp int, vt reflect.Type) {
func (self *_Compiler) compileMap(p *_Program, sp int, vt reflect.Type) {
if reflect.PtrTo(vt.Key()).Implements(encodingTextUnmarshalerType) {
self.compileMapOp(p, sp, vt, _OP_map_key_utext_p)
} else if vt.Key().Implements(encodingTextUnmarshalerType) {
Expand All @@ -575,7 +592,7 @@ func (self _Compiler) compileMap(p *_Program, sp int, vt reflect.Type) {
}
}

func (self _Compiler) compileMapUt(p *_Program, sp int, vt reflect.Type) {
func (self *_Compiler) compileMapUt(p *_Program, sp int, vt reflect.Type) {
switch vt.Key().Kind() {
case reflect.Int : self.compileMapOp(p, sp, vt, _OP_map_key_int())
case reflect.Int8 : self.compileMapOp(p, sp, vt, _OP_map_key_i8)
Expand All @@ -595,7 +612,7 @@ func (self _Compiler) compileMapUt(p *_Program, sp int, vt reflect.Type) {
}
}

func (self _Compiler) compileMapOp(p *_Program, sp int, vt reflect.Type, op _Op) {
func (self *_Compiler) compileMapOp(p *_Program, sp int, vt reflect.Type, op _Op) {
i := p.pc()
p.add(_OP_is_null)
p.tag(sp + 1)
Expand Down Expand Up @@ -649,7 +666,7 @@ func (self _Compiler) compileMapOp(p *_Program, sp int, vt reflect.Type, op _Op)
p.pin(x)
}

func (self _Compiler) compilePtr(p *_Program, sp int, et reflect.Type) {
func (self *_Compiler) compilePtr(p *_Program, sp int, et reflect.Type) {
i := p.pc()
p.add(_OP_is_null)

Expand All @@ -668,7 +685,7 @@ func (self _Compiler) compilePtr(p *_Program, sp int, et reflect.Type) {
p.pin(j)
}

func (self _Compiler) compileArray(p *_Program, sp int, vt reflect.Type) {
func (self *_Compiler) compileArray(p *_Program, sp int, vt reflect.Type) {
x := p.pc()
p.add(_OP_is_null)
p.tag(sp)
Expand Down Expand Up @@ -708,15 +725,15 @@ func (self _Compiler) compileArray(p *_Program, sp int, vt reflect.Type) {
p.pin(x)
}

func (self _Compiler) compileSlice(p *_Program, sp int, et reflect.Type) {
func (self *_Compiler) compileSlice(p *_Program, sp int, et reflect.Type) {
if et.Kind() == byteType.Kind() {
self.compileSliceBin(p, sp, et)
} else {
self.compileSliceList(p, sp, et)
}
}

func (self _Compiler) compileSliceBin(p *_Program, sp int, et reflect.Type) {
func (self *_Compiler) compileSliceBin(p *_Program, sp int, et reflect.Type) {
i := p.pc()
p.add(_OP_is_null)
j := p.pc()
Expand All @@ -738,7 +755,7 @@ func (self _Compiler) compileSliceBin(p *_Program, sp int, et reflect.Type) {
p.pin(y)
}

func (self _Compiler) compileSliceList(p *_Program, sp int, et reflect.Type) {
func (self *_Compiler) compileSliceList(p *_Program, sp int, et reflect.Type) {
i := p.pc()
p.add(_OP_is_null)
p.tag(sp)
Expand All @@ -751,7 +768,7 @@ func (self _Compiler) compileSliceList(p *_Program, sp int, et reflect.Type) {
p.pin(x)
}

func (self _Compiler) compileSliceBody(p *_Program, sp int, et reflect.Type) {
func (self *_Compiler) compileSliceBody(p *_Program, sp int, et reflect.Type) {
p.rtt(_OP_slice_init, et)
p.add(_OP_save)
p.add(_OP_lspace)
Expand All @@ -774,31 +791,34 @@ func (self _Compiler) compileSliceBody(p *_Program, sp int, et reflect.Type) {
p.add(_OP_drop)
}

func (self _Compiler) compileString(p *_Program, vt reflect.Type) {
func (self *_Compiler) compileString(p *_Program, vt reflect.Type) {
if vt == jsonNumberType {
self.compilePrimitive(p, _OP_num)
} else {
self.compileStringBody(p)
}
}

func (self _Compiler) compileStringBody(p *_Program) {
func (self *_Compiler) compileStringBody(p *_Program) {
i := p.pc()
p.add(_OP_is_null)
p.chr(_OP_match_char, '"')
p.add(_OP_str)
p.pin(i)
}

func (self _Compiler) compileStruct(p *_Program, sp int, vt reflect.Type) {
func (self *_Compiler) compileStruct(p *_Program, sp int, vt reflect.Type) {
if sp >= _MAX_STACK || p.pc() >= _MAX_ILBUF {
p.rtt(_OP_recurse, vt)
if self.opts.RecursiveDepth > 0 {
self.rec[vt] = true
}
} else {
self.compileStructBody(p, sp, vt)
}
}

func (self _Compiler) compileStructBody(p *_Program, sp int, vt reflect.Type) {
func (self *_Compiler) compileStructBody(p *_Program, sp int, vt reflect.Type) {
fv := resolver.ResolveStruct(vt)
fm, sw := caching.CreateFieldMap(len(fv)), make([]int, len(fv))

Expand Down Expand Up @@ -870,7 +890,7 @@ end_of_object:
p.pin(n)
}

func (self _Compiler) compileStructFieldStr(p *_Program, sp int, vt reflect.Type) {
func (self *_Compiler) compileStructFieldStr(p *_Program, sp int, vt reflect.Type) {
n1 := -1
ft := vt
sv := false
Expand Down Expand Up @@ -977,7 +997,7 @@ func (self _Compiler) compileStructFieldStr(p *_Program, sp int, vt reflect.Type
p.pin(pc)
}

func (self _Compiler) compileInterface(p *_Program, vt reflect.Type) {
func (self *_Compiler) compileInterface(p *_Program, vt reflect.Type) {
i := p.pc()
p.add(_OP_is_null)

Expand All @@ -996,14 +1016,14 @@ func (self _Compiler) compileInterface(p *_Program, vt reflect.Type) {
p.pin(j)
}

func (self _Compiler) compilePrimitive(p *_Program, op _Op) {
func (self *_Compiler) compilePrimitive(p *_Program, op _Op) {
i := p.pc()
p.add(_OP_is_null)
p.add(op)
p.pin(i)
}

func (self _Compiler) compileUnmarshalEnd(p *_Program, vt reflect.Type, i int) {
func (self *_Compiler) compileUnmarshalEnd(p *_Program, vt reflect.Type, i int) {
j := p.pc()
k := vt.Kind()

Expand All @@ -1020,7 +1040,7 @@ func (self _Compiler) compileUnmarshalEnd(p *_Program, vt reflect.Type, i int) {
p.pin(j)
}

func (self _Compiler) compileUnmarshalJson(p *_Program, vt reflect.Type) {
func (self *_Compiler) compileUnmarshalJson(p *_Program, vt reflect.Type) {
i := p.pc()
v := _OP_unmarshal
p.add(_OP_is_null)
Expand All @@ -1035,7 +1055,7 @@ func (self _Compiler) compileUnmarshalJson(p *_Program, vt reflect.Type) {
self.compileUnmarshalEnd(p, vt, i)
}

func (self _Compiler) compileUnmarshalText(p *_Program, vt reflect.Type) {
func (self *_Compiler) compileUnmarshalText(p *_Program, vt reflect.Type) {
i := p.pc()
v := _OP_unmarshal_text
p.add(_OP_is_null)
Expand All @@ -1052,7 +1072,7 @@ func (self _Compiler) compileUnmarshalText(p *_Program, vt reflect.Type) {
self.compileUnmarshalEnd(p, vt, i)
}

func (self _Compiler) compileUnmarshalTextPtr(p *_Program, vt reflect.Type) {
func (self *_Compiler) compileUnmarshalTextPtr(p *_Program, vt reflect.Type) {
i := p.pc()
p.add(_OP_is_null)
p.chr(_OP_match_char, '"')
Expand Down
2 changes: 1 addition & 1 deletion decoder/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
)

func TestCompiler_Compile(t *testing.T) {
prg, err := make(_Compiler).compile(reflect.TypeOf(TwitterStruct{}))
prg, err := newCompiler().compile(reflect.TypeOf(TwitterStruct{}))
assert.Nil(t, err)
prg.disassemble()
}
56 changes: 53 additions & 3 deletions decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
`runtime`

`github.com/bytedance/sonic/internal/rt`
`github.com/bytedance/sonic/option`
)

const (
Expand Down Expand Up @@ -106,7 +107,56 @@ func (self *Decoder) DisallowUnknownFields() {

// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
// order to reduce the first-hit latency.
func Pretouch(vt reflect.Type) (err error) {
_, err = findOrCompile(rt.UnpackType(vt))
return
//
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
// a compile option to set the depth of recursive compile for the nested struct type.
func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
cfg := option.DefaultCompileOptions()
for _, opt := range opts {
opt(&cfg)
break
}
return pretouchRec(map[reflect.Type]bool{vt:true}, cfg)
}

func pretouchType(_vt reflect.Type, opts option.CompileOptions) (map[reflect.Type]bool, error) {
/* compile function */
compiler := newCompiler().apply(opts)

/* compile function */
decoder := func(vt *rt.GoType) (interface{}, error) {
if pp, err := compiler.compile(_vt); err != nil {
return nil, err
} else {
return newAssembler(pp).Load(), nil
}
}

/* find or compile */
vt := rt.UnpackType(_vt)
if val := programCache.Get(vt); val != nil {
return nil, nil
} else if _, err := programCache.Compute(vt, decoder); err == nil {
return compiler.rec, nil
} else {
return nil, err
}
}

func pretouchRec(vtm map[reflect.Type]bool, opts option.CompileOptions) error {
if opts.RecursiveDepth < 0 || len(vtm) == 0 {
return nil
}
next := make(map[reflect.Type]bool)
for vt, _ := range(vtm) {
sub, err := pretouchType(vt, opts)
if err != nil {
return err
}
for svt, _ := range(sub) {
next[svt] = true
}
}
opts.RecursiveDepth -= 1
return pretouchRec(next, opts)
}
2 changes: 1 addition & 1 deletion decoder/pools.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func referenceFields(v *caching.FieldMap) int64 {
}

func makeDecoder(vt *rt.GoType) (interface{}, error) {
if pp, err := make(_Compiler).compile(vt.Pack()); err != nil {
if pp, err := newCompiler().compile(vt.Pack()); err != nil {
return nil, err
} else {
return newAssembler(pp).Load(), nil
Expand Down
Loading