mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-02-06 18:40:55 +00:00
Stash fast-path information for extensions on the ExtensionInfo. In the usual case where an ExtensionType's underlying implementation is an *ExtensionInfo, fetching the fast-path information becomes a type assertion rather than a mutex-guarded map access. Maintain a global sync.Map for the case where an ExtensionType isn't an *ExtensionInfo. Substantially improves performance for fast-path operations on extensions: Encode/MessageSet_type_id_before_message_content-12 267ns ± 1% 185ns ± 1% -30.44% (p=0.001 n=7+7) Encode/basic_scalar_types_(*test.TestAllExtensions)-12 1.94µs ± 1% 0.40µs ± 1% -79.32% (p=0.000 n=8+7) Change-Id: If048b521deb3665a090ea3d0a178c61691d4201e Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/210540 Reviewed-by: Joe Tsai <joetsai@google.com>
141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package impl
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"google.golang.org/protobuf/internal/errors"
|
|
pref "google.golang.org/protobuf/reflect/protoreflect"
|
|
)
|
|
|
|
func (mi *MessageInfo) isInitialized(m pref.Message) error {
|
|
var p pointer
|
|
if ms, ok := m.(*messageState); ok {
|
|
p = ms.pointer()
|
|
} else {
|
|
p = m.(*messageReflectWrapper).pointer()
|
|
}
|
|
return mi.isInitializedPointer(p)
|
|
}
|
|
|
|
func (mi *MessageInfo) isInitializedPointer(p pointer) error {
|
|
mi.init()
|
|
if !mi.needsInitCheck {
|
|
return nil
|
|
}
|
|
if p.IsNil() {
|
|
for _, f := range mi.orderedCoderFields {
|
|
if f.isRequired {
|
|
return errors.RequiredNotSet(string(mi.Desc.Fields().ByNumber(f.num).FullName()))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
if mi.extensionOffset.IsValid() {
|
|
e := p.Apply(mi.extensionOffset).Extensions()
|
|
if err := mi.isInitExtensions(e); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, f := range mi.orderedCoderFields {
|
|
if !f.isRequired && f.funcs.isInit == nil {
|
|
continue
|
|
}
|
|
fptr := p.Apply(f.offset)
|
|
if f.isPointer && fptr.Elem().IsNil() {
|
|
if f.isRequired {
|
|
return errors.RequiredNotSet(string(mi.Desc.Fields().ByNumber(f.num).FullName()))
|
|
}
|
|
continue
|
|
}
|
|
if f.funcs.isInit == nil {
|
|
continue
|
|
}
|
|
if err := f.funcs.isInit(fptr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mi *MessageInfo) isInitExtensions(ext *map[int32]ExtensionField) error {
|
|
if ext == nil {
|
|
return nil
|
|
}
|
|
for _, x := range *ext {
|
|
ei := getExtensionFieldInfo(x.Type())
|
|
if ei.funcs.isInit == nil {
|
|
continue
|
|
}
|
|
v := x.Value()
|
|
if !v.IsValid() {
|
|
continue
|
|
}
|
|
if err := ei.funcs.isInit(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
needsInitCheckMu sync.Mutex
|
|
needsInitCheckMap sync.Map
|
|
)
|
|
|
|
// needsInitCheck reports whether a message needs to be checked for partial initialization.
|
|
//
|
|
// It returns true if the message transitively includes any required or extension fields.
|
|
func needsInitCheck(md pref.MessageDescriptor) bool {
|
|
if v, ok := needsInitCheckMap.Load(md); ok {
|
|
if has, ok := v.(bool); ok {
|
|
return has
|
|
}
|
|
}
|
|
needsInitCheckMu.Lock()
|
|
defer needsInitCheckMu.Unlock()
|
|
return needsInitCheckLocked(md)
|
|
}
|
|
|
|
func needsInitCheckLocked(md pref.MessageDescriptor) (has bool) {
|
|
if v, ok := needsInitCheckMap.Load(md); ok {
|
|
// If has is true, we've previously determined that this message
|
|
// needs init checks.
|
|
//
|
|
// If has is false, we've previously determined that it can never
|
|
// be uninitialized.
|
|
//
|
|
// If has is not a bool, we've just encountered a cycle in the
|
|
// message graph. In this case, it is safe to return false: If
|
|
// the message does have required fields, we'll detect them later
|
|
// in the graph traversal.
|
|
has, ok := v.(bool)
|
|
return ok && has
|
|
}
|
|
needsInitCheckMap.Store(md, struct{}{}) // avoid cycles while descending into this message
|
|
defer func() {
|
|
needsInitCheckMap.Store(md, has)
|
|
}()
|
|
if md.RequiredNumbers().Len() > 0 {
|
|
return true
|
|
}
|
|
if md.ExtensionRanges().Len() > 0 {
|
|
return true
|
|
}
|
|
for i := 0; i < md.Fields().Len(); i++ {
|
|
fd := md.Fields().Get(i)
|
|
// Map keys are never messages, so just consider the map value.
|
|
if fd.IsMap() {
|
|
fd = fd.MapValue()
|
|
}
|
|
fmd := fd.Message()
|
|
if fmd != nil && needsInitCheckLocked(fmd) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|