proto, runtime/protoiface: add support for fast-path marshaling
Allow message implementations to provide optimized versions of standard
operations. Generated messages now include a ProtoReflectMethods method,
returning a protoiface.Methods struct containing pointers to assorted
optional functions.
The Methods struct also includes a Flags field indicating support for
optional features such as deterministic marshaling.
Implementation of the fast paths (and tests) will come in later CLs.
Change-Id: Idd1beed0ecf43ec5e5e7b8da2ee1e08d3ce32213
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/170340
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/proto/encode.go b/proto/encode.go
index 68a3448..973e53a 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -12,6 +12,7 @@
"github.com/golang/protobuf/v2/internal/mapsort"
"github.com/golang/protobuf/v2/internal/pragma"
"github.com/golang/protobuf/v2/reflect/protoreflect"
+ "github.com/golang/protobuf/v2/runtime/protoiface"
)
// MarshalOptions configures the marshaler.
@@ -40,9 +41,15 @@
// detail and subject to change.
Deterministic bool
+ // Reflection forces use of the reflection-based encoder, even for
+ // messages which implement fast-path serialization.
+ Reflection bool
+
pragma.NoUnkeyedLiterals
}
+var _ = protoiface.MarshalOptions(MarshalOptions{})
+
// Marshal returns the wire-format encoding of m.
func Marshal(m Message) ([]byte, error) {
return MarshalOptions{}.MarshalAppend(nil, m)
@@ -50,15 +57,39 @@
// Marshal returns the wire-format encoding of m.
func (o MarshalOptions) Marshal(m Message) ([]byte, error) {
- return o.marshalMessage(nil, m.ProtoReflect())
+ return o.MarshalAppend(nil, m)
}
// MarshalAppend appends the wire-format encoding of m to b,
// returning the result.
func (o MarshalOptions) MarshalAppend(b []byte, m Message) ([]byte, error) {
+ if b, err := o.marshalMessageFast(b, m); err != errInternalNoFast {
+ return b, err
+ }
return o.marshalMessage(b, m.ProtoReflect())
}
+func (o MarshalOptions) marshalMessageFast(b []byte, m Message) ([]byte, error) {
+ if o.Reflection {
+ return nil, errInternalNoFast
+ }
+ methods := protoMethods(m)
+ if methods == nil ||
+ methods.MarshalAppend == nil ||
+ (o.Deterministic && methods.Flags&protoiface.MethodFlagDeterministicMarshal == 0) {
+ return nil, errInternalNoFast
+ }
+ if methods.Size != nil {
+ sz := methods.Size(m)
+ if cap(b) < len(b)+sz {
+ x := make([]byte, len(b), len(b)+sz)
+ copy(x, b)
+ b = x
+ }
+ }
+ return methods.MarshalAppend(b, m, protoiface.MarshalOptions(o))
+}
+
func (o MarshalOptions) marshalMessage(b []byte, m protoreflect.Message) ([]byte, error) {
// There are many choices for what order we visit fields in. The default one here
// is chosen for reasonable efficiency and simplicity given the protoreflect API.