Repository: bytedance/sonic
Pull request #287 (2022-08-19 09:10:38)

feat:(option) add option `MaxInlineDepth` for addjust compilation inline depth
## feature
- default inline depth `option.DefaultMaxInineDepth` is changed to 3 instead of 5 and is changeable to user
- default pretouch recurse times  `option.DefaultRecurseDepth` is changed to 1 instead of 0 and is changeable to user
- `Pretouch()` will pretouch concrete type and pointer type both for one call now
- add internal constant `_MAX_FIELDS` to limit inlining of big struct


Patch:
diff --git a/README.md b/README.md
index 55d12ff..77e1ce2 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 97d5708..b18e938 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 e9f042c..16c8592 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 41727b3..238bfc1 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 043b8a7..0b9fd31 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/licenses/LICENSE-golang-asm b/licenses/LICENSE-golang-asm
new file mode 100644
index 0000000..ea5ea89
--- /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 3b8366f..359f992 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 9c88e95..99b1dad 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,


Test patch:
diff --git a/issue_test/pretouch_test.go b/issue_test/pretouch_test.go
new file mode 100644
index 0000000..c87e361
--- /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