From 49fc705341c13f2fa5c81b58a17bdfd5047d0589 Mon Sep 17 00:00:00 2001 From: liu Date: Fri, 26 Nov 2021 14:12:51 +0800 Subject: [PATCH] feat: Pretouch recursively for large/deep struct (#137) --- decoder/assembler_test.go | 8 ++-- decoder/compiler.go | 82 +++++++++++++++++++++++-------------- decoder/compiler_test.go | 2 +- decoder/decoder.go | 56 +++++++++++++++++++++++-- decoder/pools.go | 2 +- encoder/compiler.go | 18 +++++++- encoder/encoder.go | 54 ++++++++++++++++++++++-- issue_test/issue138_test.go | 79 +++++++++++++++++++++++++++++++++++ option/option.go | 45 ++++++++++++++++++++ sonic.go | 10 +++-- 10 files changed, 308 insertions(+), 48 deletions(-) create mode 100644 issue_test/issue138_test.go create mode 100644 option/option.go diff --git a/decoder/assembler_test.go b/decoder/assembler_test.go index dc228c839..21a602694 100644 --- a/decoder/assembler_test.go +++ b/decoder/assembler_test.go @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/decoder/compiler.go b/decoder/compiler.go index 263ec506b..aa94f0bc2 100644 --- a/decoder/compiler.go +++ b/decoder/compiler.go @@ -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 @@ -389,7 +390,7 @@ func (self _Instr) formatStructFields() string { } type ( - _Program []_Instr + _Program []_Instr ) func (self _Program) pc() int { @@ -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 @@ -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 */ @@ -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()) @@ -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) { @@ -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) @@ -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) @@ -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) @@ -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) @@ -708,7 +725,7 @@ 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 { @@ -716,7 +733,7 @@ func (self _Compiler) compileSlice(p *_Program, sp int, et reflect.Type) { } } -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() @@ -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) @@ -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) @@ -774,7 +791,7 @@ 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 { @@ -782,7 +799,7 @@ func (self _Compiler) compileString(p *_Program, vt reflect.Type) { } } -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, '"') @@ -790,15 +807,18 @@ func (self _Compiler) compileStringBody(p *_Program) { 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)) @@ -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 @@ -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) @@ -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() @@ -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) @@ -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) @@ -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, '"') diff --git a/decoder/compiler_test.go b/decoder/compiler_test.go index fb8ef76fe..4faa127da 100644 --- a/decoder/compiler_test.go +++ b/decoder/compiler_test.go @@ -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() } diff --git a/decoder/decoder.go b/decoder/decoder.go index 9dc430fc3..232e0ca95 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -22,6 +22,7 @@ import ( `runtime` `github.com/bytedance/sonic/internal/rt` + `github.com/bytedance/sonic/option` ) const ( @@ -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) } \ No newline at end of file diff --git a/decoder/pools.go b/decoder/pools.go index 81cb2fa05..9dc457404 100644 --- a/decoder/pools.go +++ b/decoder/pools.go @@ -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 diff --git a/encoder/compiler.go b/encoder/compiler.go index 56358d726..ed79795db 100644 --- a/encoder/compiler.go +++ b/encoder/compiler.go @@ -25,6 +25,7 @@ import ( `github.com/bytedance/sonic/internal/resolver` `github.com/bytedance/sonic/internal/rt` + `github.com/bytedance/sonic/option` ) type _Op uint8 @@ -371,8 +372,10 @@ func (self _Program) disassemble() string { } type _Compiler struct { - pv bool - tab map[reflect.Type]bool + opts option.CompileOptions + pv bool + tab map[reflect.Type]bool + rec map[reflect.Type]bool } func newCompiler() *_Compiler { @@ -381,6 +384,14 @@ func newCompiler() *_Compiler { } } +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 { @@ -645,6 +656,9 @@ func (self *_Compiler) compileString(p *_Program, 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) } diff --git a/encoder/encoder.go b/encoder/encoder.go index 61d4c874a..0300a8560 100644 --- a/encoder/encoder.go +++ b/encoder/encoder.go @@ -23,6 +23,7 @@ import ( `runtime` `github.com/bytedance/sonic/internal/rt` + `github.com/bytedance/sonic/option` ) // Options is a set of encoding options. @@ -150,7 +151,54 @@ func EncodeIndented(val interface{}, prefix string, indent string, opts Options) // 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) + encoder := 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, encoder); 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) +} \ No newline at end of file diff --git a/issue_test/issue138_test.go b/issue_test/issue138_test.go new file mode 100644 index 000000000..76bbb9fa6 --- /dev/null +++ b/issue_test/issue138_test.go @@ -0,0 +1,79 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package issue_test + +import ( + `fmt` + `time` + `testing` + `reflect` + + `github.com/stretchr/testify/require` + `github.com/bytedance/sonic` + `github.com/bytedance/sonic/option` +) + +type Issue138_DeepStruct struct { + L0 struct { + L1 struct { + L2 struct { + L3 struct { + L4 struct { + L5 struct { + L6 struct { + L7 struct { + L8 struct { + L9 struct { + L10 struct { + L11 struct { + L12 struct { + L13 struct { + L14 struct { + L15 struct { + L16 struct { + L17 struct { + L18 struct { + L19 struct { + L20 struct { + L21 struct { + L22 struct { + A int + B string + C []float64 + E map[string]bool + F *Issue138_DeepStruct + }}}}}}}}}}}}}}}}}}}}}}} +} + +func testPretouchTime(depth int) { + start := time.Now() + sonic.Pretouch(reflect.TypeOf(Issue138_DeepStruct{}), option.WithCompileRecursiveDepth(depth)) + elapsed := time.Since(start) + fmt.Printf("Pretouch with recursive depth %d, time is %s\n", depth, elapsed) +} + +func TestIssue138_PretouchTime(t *testing.T) { + testPretouchTime(4) + var obj Issue138_DeepStruct + start := time.Now() + data, err := sonic.Marshal(obj) + err = sonic.Unmarshal([]byte(data), &obj) + elapsed := time.Since(start) + fmt.Printf("Marshal and unmarshal time is %s\n", elapsed) + require.NoError(t, err) +} + diff --git a/option/option.go b/option/option.go new file mode 100644 index 000000000..3b8366f72 --- /dev/null +++ b/option/option.go @@ -0,0 +1,45 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package option + +// CompileOptions includes all options for encoder or decoder compiler. +type CompileOptions struct { + // the depth for recursive compile + RecursiveDepth int +} + +func DefaultCompileOptions() CompileOptions { + return CompileOptions{ + RecursiveDepth: 0, + } +} + +type CompileOption func(o *CompileOptions) + +// WithCompileRecursiveDepth sets the depth of recursive compile +// in decoder or encoder. +// +// Default value(0) is suitable for basic types and small nested struct types. +// +// For large or deep nested struct, try to set larger depth to reduce compile +// time in the first Marshal or Unmarshal. +func WithCompileRecursiveDepth(depth int) CompileOption { + return func(o *CompileOptions) { + o.RecursiveDepth = depth + } +} + \ No newline at end of file diff --git a/sonic.go b/sonic.go index 61bb473d0..a7dc37933 100644 --- a/sonic.go +++ b/sonic.go @@ -23,6 +23,7 @@ import ( `github.com/bytedance/sonic/ast` `github.com/bytedance/sonic/decoder` `github.com/bytedance/sonic/encoder` + `github.com/bytedance/sonic/option` `github.com/bytedance/sonic/internal/native/types` ) @@ -74,10 +75,13 @@ func UnmarshalString(buf string, val interface{}) error { // 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) error { - if err := encoder.Pretouch(vt); err != nil { +// +// 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 { + if err := encoder.Pretouch(vt, opts...); err != nil { return err - } else if err = decoder.Pretouch(vt); err != nil { + } else if err = decoder.Pretouch(vt, opts...); err != nil { return err } else { return nil