2
0
Fork 0
mirror of https://github.com/ii64/sonic.git synced 2026-06-20 16:45:22 +08:00

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
This commit is contained in:
Yi Duan 2022-08-22 15:45:49 +08:00 committed by GitHub
parent 1a7758c557
commit 94f95f0479
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 490 additions and 36 deletions

View file

@ -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),
)
}
```

View file

@ -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

View file

@ -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
}
}

View file

@ -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

View file

@ -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
}
}

375
issue_test/pretouch_test.go Normal file
View file

@ -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)
}
}

View file

@ -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.

View file

@ -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.
//
// 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 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) {
o.RecursiveDepth = depth
if loop < 0 {
panic("loop must be >= 0")
}
o.RecursiveDepth = loop
}
}
// WithCompileMaxInlineDepth sets the max depth of inline compile
// in decoder and encoder.
//
// For large nested struct, try to set smaller depth to reduce compiling time.
func WithCompileMaxInlineDepth(depth int) CompileOption {
return func(o *CompileOptions) {
if depth <= 0 {
panic("depth must be > 0")
}
o.MaxInlineDepth = depth
}
}

View file

@ -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,