Skip to content

Commit

Permalink
feat: Pretouch recursively for large/deep struct (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
liuq19 authored Nov 26, 2021
1 parent 9a95e9d commit 49fc705
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 48 deletions.
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

0 comments on commit 49fc705

Please sign in to comment.