mirror of
https://github.com/golang/go.git
synced 2026-06-28 03:40:37 +00:00
encoding/json/v2: add string option hint optimization
Starting with CL 779300, we return an error if fields with a `string` tag do not have a supported type. Most field validation occurs in makeStructFields. makeStructFields runs in a per-type sync.Once, with errors reported during each Marshal/Unmarshal call before any marshaling or unmarshaling begins. The string validation semantics depend on the StringifyWithLegacySemantics option, so the full validation cannot occur inside the sync.Once. Instead each Marshal/Unmarshal call must loop over every field to perform validation. We must use a second loop over the fields rather than performing validation inline inside the main marshal loop to match the other field validation semantic of reporting errors before marshaling begins. The loop isn't very expensive, but it does double the number of times we need to loop at each struct field. Package benchmarks did not show any statistically significant regressions on specific benchmarks (all regressions I saw disappear when running just that benchmark with more iterations). Regardless, BenchmarkUnmarshal/Struct/ManySmall spends ~2% of CPU time on this loop. Most structs won't have any fields with a string tag, so add a simple hint for when at least one field has the tag, and skip the validation loop otherwise. For #79065. Change-Id: I6f8b7d8c120c43e68b32dd1b19107d266a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/779940 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Michael Pratt <mpratt@google.com> Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
469636308b
commit
4b77d329ea
2 changed files with 10 additions and 2 deletions
|
|
@ -1085,7 +1085,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
|
|||
// This validation is effectively a makeStructFields error that
|
||||
// occurs before any marshalling begins, but since it depends
|
||||
// on the marshal options it can't be part of the sync.Once.
|
||||
if !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
if fields.hasString && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
for i := range fields.flattened {
|
||||
f := &fields.flattened[i]
|
||||
if f.string {
|
||||
|
|
@ -1290,7 +1290,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
|
|||
// This validation is effectively a makeStructFields error that
|
||||
// occurs before any marshalling begins, but since it depends
|
||||
// on the marshal options it can't be part of the sync.Once.
|
||||
if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
if fields.hasString && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
for i := range fields.flattened {
|
||||
f := &fields.flattened[i]
|
||||
if f.string {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type structFields struct {
|
|||
byActualName map[string]*structField
|
||||
byFoldedName map[string][]*structField
|
||||
inlinedFallback *structField
|
||||
hasString bool // one or more fields set the string option
|
||||
}
|
||||
|
||||
// reindex recomputes index to avoid bounds check during runtime.
|
||||
|
|
@ -81,6 +82,9 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
|
|||
return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)})
|
||||
}
|
||||
|
||||
// Whether any field sets the string option.
|
||||
var hasString bool
|
||||
|
||||
// Setup a queue for a breadth-first search.
|
||||
var queueIndex int
|
||||
type queueEntry struct {
|
||||
|
|
@ -248,6 +252,9 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
|
|||
f.id = len(allFields)
|
||||
f.fncs = lookupArshaler(sf.Type)
|
||||
allFields = append(allFields, f)
|
||||
if f.string {
|
||||
hasString = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -313,6 +320,7 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
|
|||
flattened: flattened,
|
||||
byActualName: make(map[string]*structField, len(flattened)),
|
||||
byFoldedName: make(map[string][]*structField, len(flattened)),
|
||||
hasString: hasString,
|
||||
}
|
||||
for i, f := range fs.flattened {
|
||||
foldedName := string(foldName([]byte(f.name)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue