From 94f95f0479aa3424678acb7ba4fe31b301caca40 Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Mon, 22 Aug 2022 15:45:49 +0800 Subject: [PATCH] feat:(option) add option `MaxInlineDepth` for addjust compilation inline depth (#287) * feat: make compilation depth changeable * feat: add option `DefaultMaxInlineDepth` * add recurse depth = 10 * refactor * doc: readme and comment * opt: add `_MAX_FIELDS` to limit the inlining of big struct * update license * fix typo --- README.md | 15 +- decoder/compiler.go | 15 +- decoder/decoder.go | 5 +- encoder/compiler.go | 6 +- encoder/encoder.go | 4 +- issue_test/pretouch_test.go | 375 ++++++++++++++++++++++++++++++++++++ licenses/LICENSE-golang-asm | 27 +++ option/option.go | 53 ++++- sonic.go | 26 ++- 9 files changed, 490 insertions(+), 36 deletions(-) create mode 100644 issue_test/pretouch_test.go create mode 100644 licenses/LICENSE-golang-asm diff --git a/README.md b/README.md index 55d12ffe2..77e1ce2bc 100644 --- a/README.md +++ b/README.md @@ -284,11 +284,18 @@ import ( func init() { var v HugeStruct - // For most large types (nesting depth <= 5) + + // For most large types (nesting depth <= option.DefaultMaxInlineDepth) err := sonic.Pretouch(reflect.TypeOf(v)) - // If the type is too deep nesting (nesting depth > 5), - // you can set compile recursive depth in Pretouch for better stability in JIT. - err := sonic.Pretouch(reflect.TypeOf(v), option.WithCompileRecursiveDepth(depth)) + + // with more CompileOption... + err := sonic.Pretouch(reflect.TypeOf(v), + // If the type is too deep nesting (nesting depth > option.DefaultMaxInlineDepth), + // you can set compile recursive loops in Pretouch for better stability in JIT. + option.WithCompileRecursiveDepth(loop), + // For large nested struct, try to set smaller depth to reduce compiling time. + option.WithCompileMaxInlineDepth(depth), + ) } ``` diff --git a/decoder/compiler.go b/decoder/compiler.go index 97d5708ad..b18e9382a 100644 --- a/decoder/compiler.go +++ b/decoder/compiler.go @@ -102,8 +102,8 @@ const ( ) const ( - _MAX_STACK = 5 // cutoff at 5 levels of nesting types _MAX_ILBUF = 100000 // cutoff at 100k of IL instructions + _MAX_FIELDS = 50 // cutoff at 50 fields struct ) var _OpNames = [256]string { @@ -487,15 +487,14 @@ type _Compiler struct { func newCompiler() *_Compiler { return &_Compiler { + opts: option.DefaultCompileOptions(), tab: map[reflect.Type]bool{}, + rec: map[reflect.Type]bool{}, } } func (self *_Compiler) apply(opts option.CompileOptions) *_Compiler { self.opts = opts - if self.opts.RecursiveDepth > 0 { - self.rec = map[reflect.Type]bool{} - } return self } @@ -516,15 +515,15 @@ func (self *_Compiler) compile(vt reflect.Type) (ret _Program, err error) { } func (self *_Compiler) compileOne(p *_Program, sp int, vt reflect.Type) { - ok := self.tab[vt] - pt := reflect.PtrTo(vt) - /* check for recursive nesting */ + ok := self.tab[vt] if ok { p.rtt(_OP_recurse, vt) return } + pt := reflect.PtrTo(vt) + /* check for `json.Unmarshaler` with pointer receiver */ if pt.Implements(jsonUnmarshalerType) { p.rtt(_OP_unmarshal_p, pt) @@ -812,7 +811,7 @@ func (self *_Compiler) compileStringBody(p *_Program) { } func (self *_Compiler) compileStruct(p *_Program, sp int, vt reflect.Type) { - if sp >= _MAX_STACK || p.pc() >= _MAX_ILBUF { + if sp >= self.opts.MaxInlineDepth || p.pc() >= _MAX_ILBUF || (sp > 0 && vt.NumField() >= _MAX_FIELDS) { p.rtt(_OP_recurse, vt) if self.opts.RecursiveDepth > 0 { self.rec[vt] = true diff --git a/decoder/decoder.go b/decoder/decoder.go index e9f042c6d..16c8592d7 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -157,7 +157,6 @@ 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) } @@ -189,12 +188,12 @@ func pretouchRec(vtm map[reflect.Type]bool, opts option.CompileOptions) error { return nil } next := make(map[reflect.Type]bool) - for vt, _ := range(vtm) { + for vt := range(vtm) { sub, err := pretouchType(vt, opts) if err != nil { return err } - for svt, _ := range(sub) { + for svt := range(sub) { next[svt] = true } } diff --git a/encoder/compiler.go b/encoder/compiler.go index 41727b373..238bfc1d3 100644 --- a/encoder/compiler.go +++ b/encoder/compiler.go @@ -89,8 +89,8 @@ const ( ) const ( - _MAX_STACK = 5 // cutoff at 5 levels of nesting types _MAX_ILBUF = 100000 // cutoff at 100k of IL instructions + _MAX_FIELDS = 50 // cutoff at 50 fields struct ) var _OpNames = [256]string { @@ -384,7 +384,9 @@ type _Compiler struct { func newCompiler() *_Compiler { return &_Compiler { + opts: option.DefaultCompileOptions(), tab: map[reflect.Type]bool{}, + rec: map[reflect.Type]bool{}, } } @@ -658,7 +660,7 @@ 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 { + if sp >= self.opts.MaxInlineDepth || p.pc() >= _MAX_ILBUF || (sp > 0 && vt.NumField() >= _MAX_FIELDS) { p.rtt(_OP_recurse, vt) if self.opts.RecursiveDepth > 0 { self.rec[vt] = true diff --git a/encoder/encoder.go b/encoder/encoder.go index 043b8a76f..0b9fd317f 100644 --- a/encoder/encoder.go +++ b/encoder/encoder.go @@ -314,12 +314,12 @@ func pretouchRec(vtm map[reflect.Type]bool, opts option.CompileOptions) error { return nil } next := make(map[reflect.Type]bool) - for vt, _ := range(vtm) { + for vt := range(vtm) { sub, err := pretouchType(vt, opts) if err != nil { return err } - for svt, _ := range(sub) { + for svt := range(sub) { next[svt] = true } } diff --git a/issue_test/pretouch_test.go b/issue_test/pretouch_test.go new file mode 100644 index 000000000..c87e3619d --- /dev/null +++ b/issue_test/pretouch_test.go @@ -0,0 +1,375 @@ +/* + * 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 ( + `bytes` + `compress/gzip` + `encoding/json` + `io/ioutil` + `reflect` + `testing` + `time` + + . `github.com/bytedance/sonic` + `github.com/bytedance/sonic/option` + `github.com/stretchr/testify/require` +) + +var jsonData = func() string { + // Read and decompress the test data. + b, err := ioutil.ReadFile("../testdata/synthea_fhir.json.gz") + if err != nil { + panic(err) + } + zr, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + panic(err) + } + data, err := ioutil.ReadAll(zr) + if err != nil { + panic(err) + } + return string(data) +}() + +type ( + syntheaRoot struct { + Entry []struct { + FullURL string `json:"fullUrl"` + Request *struct { + Method string `json:"method"` + URL string `json:"url"` + } `json:"request"` + Resource *struct { + AbatementDateTime time.Time `json:"abatementDateTime"` + AchievementStatus syntheaCode `json:"achievementStatus"` + Active bool `json:"active"` + Activity []struct { + Detail *struct { + Code syntheaCode `json:"code"` + Location syntheaReference `json:"location"` + Status string `json:"status"` + } `json:"detail"` + } `json:"activity"` + Address []syntheaAddress `json:"address"` + Addresses []syntheaReference `json:"addresses"` + AuthoredOn time.Time `json:"authoredOn"` + BillablePeriod syntheaRange `json:"billablePeriod"` + BirthDate string `json:"birthDate"` + CareTeam []struct { + Provider syntheaReference `json:"provider"` + Reference string `json:"reference"` + Role syntheaCode `json:"role"` + Sequence int64 `json:"sequence"` + } `json:"careTeam"` + Category []syntheaCode `json:"category"` + Claim syntheaReference `json:"claim"` + Class syntheaCoding `json:"class"` + ClinicalStatus syntheaCode `json:"clinicalStatus"` + Code syntheaCode `json:"code"` + Communication []struct { + Language syntheaCode `json:"language"` + } `json:"communication"` + Component []struct { + Code syntheaCode `json:"code"` + ValueQuantity syntheaCoding `json:"valueQuantity"` + } `json:"component"` + Contained []struct { + Beneficiary syntheaReference `json:"beneficiary"` + ID string `json:"id"` + Intent string `json:"intent"` + Payor []syntheaReference `json:"payor"` + Performer []syntheaReference `json:"performer"` + Requester syntheaReference `json:"requester"` + ResourceType string `json:"resourceType"` + Status string `json:"status"` + Subject syntheaReference `json:"subject"` + Type syntheaCode `json:"type"` + } `json:"contained"` + Created time.Time `json:"created"` + DeceasedDateTime time.Time `json:"deceasedDateTime"` + Description syntheaCode `json:"description"` + Diagnosis []struct { + DiagnosisReference syntheaReference `json:"diagnosisReference"` + Sequence int64 `json:"sequence"` + Type []syntheaCode `json:"type"` + } `json:"diagnosis"` + DosageInstruction []struct { + AsNeededBoolean bool `json:"asNeededBoolean"` + DoseAndRate []struct { + DoseQuantity *struct { + Value float64 `json:"value"` + } `json:"doseQuantity"` + Type syntheaCode `json:"type"` + } `json:"doseAndRate"` + Sequence int64 `json:"sequence"` + Timing *struct { + Repeat *struct { + Frequency int64 `json:"frequency"` + Period float64 `json:"period"` + PeriodUnit string `json:"periodUnit"` + } `json:"repeat"` + } `json:"timing"` + } `json:"dosageInstruction"` + EffectiveDateTime time.Time `json:"effectiveDateTime"` + Encounter syntheaReference `json:"encounter"` + Extension []syntheaExtension `json:"extension"` + Gender string `json:"gender"` + Goal []syntheaReference `json:"goal"` + ID string `json:"id"` + Identifier []struct { + System string `json:"system"` + Type syntheaCode `json:"type"` + Use string `json:"use"` + Value string `json:"value"` + } `json:"identifier"` + Insurance []struct { + Coverage syntheaReference `json:"coverage"` + Focal bool `json:"focal"` + Sequence int64 `json:"sequence"` + } `json:"insurance"` + Insurer syntheaReference `json:"insurer"` + Intent string `json:"intent"` + Issued time.Time `json:"issued"` + Item []struct { + Adjudication []struct { + Amount syntheaCurrency `json:"amount"` + Category syntheaCode `json:"category"` + } `json:"adjudication"` + Category syntheaCode `json:"category"` + DiagnosisSequence []int64 `json:"diagnosisSequence"` + Encounter []syntheaReference `json:"encounter"` + InformationSequence []int64 `json:"informationSequence"` + LocationCodeableConcept syntheaCode `json:"locationCodeableConcept"` + Net syntheaCurrency `json:"net"` + ProcedureSequence []int64 `json:"procedureSequence"` + ProductOrService syntheaCode `json:"productOrService"` + Sequence int64 `json:"sequence"` + ServicedPeriod syntheaRange `json:"servicedPeriod"` + } `json:"item"` + LifecycleStatus string `json:"lifecycleStatus"` + ManagingOrganization []syntheaReference `json:"managingOrganization"` + MaritalStatus syntheaCode `json:"maritalStatus"` + MedicationCodeableConcept syntheaCode `json:"medicationCodeableConcept"` + MultipleBirthBoolean bool `json:"multipleBirthBoolean"` + Name json.RawMessage `json:"name"` + NumberOfInstances int64 `json:"numberOfInstances"` + NumberOfSeries int64 `json:"numberOfSeries"` + OccurrenceDateTime time.Time `json:"occurrenceDateTime"` + OnsetDateTime time.Time `json:"onsetDateTime"` + Outcome string `json:"outcome"` + Participant []struct { + Individual syntheaReference `json:"individual"` + Member syntheaReference `json:"member"` + Role []syntheaCode `json:"role"` + } `json:"participant"` + Patient syntheaReference `json:"patient"` + Payment *struct { + Amount syntheaCurrency `json:"amount"` + } `json:"payment"` + PerformedPeriod syntheaRange `json:"performedPeriod"` + Period syntheaRange `json:"period"` + Prescription syntheaReference `json:"prescription"` + PrimarySource bool `json:"primarySource"` + Priority syntheaCode `json:"priority"` + Procedure []struct { + ProcedureReference syntheaReference `json:"procedureReference"` + Sequence int64 `json:"sequence"` + } `json:"procedure"` + Provider syntheaReference `json:"provider"` + ReasonCode []syntheaCode `json:"reasonCode"` + ReasonReference []syntheaReference `json:"reasonReference"` + RecordedDate time.Time `json:"recordedDate"` + Referral syntheaReference `json:"referral"` + Requester syntheaReference `json:"requester"` + ResourceType string `json:"resourceType"` + Result []syntheaReference `json:"result"` + Series []struct { + BodySite syntheaCoding `json:"bodySite"` + Instance []struct { + Number int64 `json:"number"` + SopClass syntheaCoding `json:"sopClass"` + Title string `json:"title"` + UID string `json:"uid"` + } `json:"instance"` + Modality syntheaCoding `json:"modality"` + Number int64 `json:"number"` + NumberOfInstances int64 `json:"numberOfInstances"` + Started string `json:"started"` + UID string `json:"uid"` + } `json:"series"` + ServiceProvider syntheaReference `json:"serviceProvider"` + Started time.Time `json:"started"` + Status string `json:"status"` + Subject syntheaReference `json:"subject"` + SupportingInfo []struct { + Category syntheaCode `json:"category"` + Sequence int64 `json:"sequence"` + ValueReference syntheaReference `json:"valueReference"` + } `json:"supportingInfo"` + Telecom []map[string]string `json:"telecom"` + Text map[string]string `json:"text"` + Total json.RawMessage `json:"total"` + Type json.RawMessage `json:"type"` + Use string `json:"use"` + VaccineCode syntheaCode `json:"vaccineCode"` + ValueCodeableConcept syntheaCode `json:"valueCodeableConcept"` + ValueQuantity syntheaCoding `json:"valueQuantity"` + VerificationStatus syntheaCode `json:"verificationStatus"` + } `json:"resource"` + } `json:"entry"` + ResourceType string `json:"resourceType"` + Type string `json:"type"` + } + syntheaCode struct { + Coding []syntheaCoding `json:"coding"` + Text string `json:"text"` + } + syntheaCoding struct { + Code string `json:"code"` + Display string `json:"display"` + System string `json:"system"` + Unit string `json:"unit"` + Value float64 `json:"value"` + } + syntheaReference struct { + Display string `json:"display"` + Reference string `json:"reference"` + } + syntheaAddress struct { + City string `json:"city"` + Country string `json:"country"` + Extension []syntheaExtension `json:"extension"` + Line []string `json:"line"` + PostalCode string `json:"postalCode"` + State string `json:"state"` + } + syntheaExtension struct { + URL string `json:"url"` + ValueAddress syntheaAddress `json:"valueAddress"` + ValueCode string `json:"valueCode"` + ValueDecimal float64 `json:"valueDecimal"` + ValueString string `json:"valueString"` + Extension []syntheaExtension `json:"extension"` + } + syntheaRange struct { + End time.Time `json:"end"` + Start time.Time `json:"start"` + } + syntheaCurrency struct { + Currency string `json:"currency"` + Value float64 `json:"value"` + } +) + + +func TestPretouchSynteaRoot(t *testing.T) { + m := new(syntheaRoot) + s := time.Now() + println("start decoder pretouch:", s.UnixNano()) + require.Nil(t, Pretouch(reflect.TypeOf(m), option.WithCompileMaxInlineDepth(2), option.WithCompileRecursiveDepth(20))) + e := time.Now() + println("end decoder pretouch:", e.UnixNano()) + println("elapsed:", e.Sub(s).Milliseconds(), "ms") + + s = time.Now() + println("start decode:", s.UnixNano()) + require.Nil(t, UnmarshalString(jsonData, m)) + e = time.Now() + println("end decode:", e.UnixNano()) + d1 := e.Sub(s).Nanoseconds() + println("elapsed:", d1, "ns") + + s = time.Now() + println("start decode:", s.UnixNano()) + require.Nil(t, UnmarshalString(jsonData, m)) + e = time.Now() + println("end decode:", e.UnixNano()) + d2 := e.Sub(s).Nanoseconds() + println("elapsed:", d2, "ns") + if d1 > d2 * 10 { + t.Fatal("decoder pretouch not finish yet") + } + + s = time.Now() + println("start decode:", s.UnixNano()) + require.Nil(t, UnmarshalString(jsonData, m)) + e = time.Now() + println("end decode:", e.UnixNano()) + d5 := e.Sub(s).Nanoseconds() + println("elapsed:", d5, "ns") + if d2 > d5 * 10 { + t.Fatal("decoder pretouch not finish yet") + } + + s = time.Now() + println("start encode 1:", s.UnixNano()) + _, err := MarshalString(*m) + require.Nil(t, err) + e = time.Now() + println("end encode 1:", e.UnixNano()) + d3 := e.Sub(s).Nanoseconds() + println("elapsed:", d3, "ns") + + s = time.Now() + println("start encode 2:", s.UnixNano()) + _, err = MarshalString(m) + require.Nil(t, err) + e = time.Now() + println("end encode 2:", e.UnixNano()) + d4 := e.Sub(s).Nanoseconds() + println("elapsed:", d4, "ns") + // if d3 > d4 * 10 { + // t.Fatal("encoder pretouch not finish yet") + // } + + s = time.Now() + println("start encode 3:", s.UnixNano()) + _, err = MarshalString(m) + require.Nil(t, err) + e = time.Now() + println("end encode 3:", e.UnixNano()) + d6 := e.Sub(s).Nanoseconds() + println("elapsed:", d6, "ns") + if d4 > d6 * 10 { + t.Fatal("encoder pretouch not finish yet") + } +} + +func BenchmarkDecodeSynteaRoot(b *testing.B) { + m := new(syntheaRoot) + require.Nil(b, Pretouch(reflect.TypeOf(m), option.WithCompileRecursiveDepth(10))) + + b.SetBytes(int64(len(jsonData))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = UnmarshalString(jsonData, m) + } +} + +func BenchmarkEncodeSynteaRoot(b *testing.B) { + m := new(syntheaRoot) + require.Nil(b, Pretouch(reflect.TypeOf(m), option.WithCompileRecursiveDepth(10))) + require.Nil(b, UnmarshalString(jsonData, m)) + + b.SetBytes(int64(len(jsonData))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = MarshalString(m) + } +} \ No newline at end of file diff --git a/licenses/LICENSE-golang-asm b/licenses/LICENSE-golang-asm new file mode 100644 index 000000000..ea5ea8986 --- /dev/null +++ b/licenses/LICENSE-golang-asm @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/option/option.go b/option/option.go index 3b8366f72..359f99206 100644 --- a/option/option.go +++ b/option/option.go @@ -18,28 +18,61 @@ package option // CompileOptions includes all options for encoder or decoder compiler. type CompileOptions struct { - // the depth for recursive compile + // the maximum depth for compilation inline + MaxInlineDepth int + + // the loop times for recursively pretouch RecursiveDepth int } +var ( + // Default value(3) means the compiler only inline 3 layers of nested struct. + // when the depth exceeds, the compiler will recurse + // and compile subsequent structs when they are decoded + DefaultMaxInlineDepth = 3 + + // Default value(1) means `Pretouch()` will be recursively executed once, + // if any nested struct is left (depth exceeds MaxInlineDepth) + DefaultRecursiveDepth = 1 +) + +// DefaultCompileOptions set default compile options. func DefaultCompileOptions() CompileOptions { return CompileOptions{ - RecursiveDepth: 0, + RecursiveDepth: DefaultRecursiveDepth, + MaxInlineDepth: DefaultMaxInlineDepth, } } +// CompileOption is a function used to change DefaultCompileOptions. type CompileOption func(o *CompileOptions) -// WithCompileRecursiveDepth sets the depth of recursive compile -// in decoder or encoder. +// WithCompileRecursiveDepth sets the loop times of recursive pretouch +// in both decoder and encoder, +// for both concrete type and its pointer type. +// +// For deep nested struct (depth exceeds MaxInlineDepth), +// try to set more loops to completely compile, +// thus reduce JIT unstability in the first hit. +func WithCompileRecursiveDepth(loop int) CompileOption { + return func(o *CompileOptions) { + if loop < 0 { + panic("loop must be >= 0") + } + o.RecursiveDepth = loop + } +} + +// WithCompileMaxInlineDepth sets the max depth of inline compile +// in decoder and 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 { +// For large nested struct, try to set smaller depth to reduce compiling time. +func WithCompileMaxInlineDepth(depth int) CompileOption { return func(o *CompileOptions) { - o.RecursiveDepth = depth + if depth <= 0 { + panic("depth must be > 0") + } + o.MaxInlineDepth = depth } } \ No newline at end of file diff --git a/sonic.go b/sonic.go index 9c88e9503..99b1dada8 100644 --- a/sonic.go +++ b/sonic.go @@ -159,13 +159,25 @@ func (cfg *frozenConfig) Valid(data []byte) bool { // 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, opts...); err != nil { - return err - } else { - return nil - } + if err := encoder.Pretouch(vt, opts...); err != nil { + return err + } + if err := decoder.Pretouch(vt, opts...); err != nil { + return err + } + // to pretouch the corresponding pointer type as well + if vt.Kind() == reflect.Ptr { + vt = vt.Elem() + } else { + vt = reflect.PtrTo(vt) + } + if err := encoder.Pretouch(vt, opts...); err != nil { + return err + } + if err := decoder.Pretouch(vt, opts...); err != nil { + return err + } + return nil } // Get searches the given path json,