Shortly, it could be done via protowire, and not really hard if structure reused isn't complex.
I asked this question not long ago, and I finally work it out inspired by @nj_ 's post. According to the encoding chapter of protobuf, a protocol buffer message is a series of field-value pairs, and the order of those pairs doesn't matter. An obvious idea comes to me: just works like the protoc compiler, make up the embedded field handly and append it to the end of the request.
In this situation, we want to reuse the HugeMessage
in Request
, so the key-value pair of the field would be 2:{${HugeMessageBinary}}
. So the code(a little different) could be:
func binaryEmbeddingImplementation(messageBytes []byte, name string) (requestBytes []byte, err error) {
// 1. create a request with all ready except the payload. and marshal it.
request := protodef.Request{
Name: name,
}
requestBytes, err = proto.Marshal(&request)
if err != nil {
return nil, err
}
// 2. manually append the payload to the request, by protowire.
requestBytes = protowire.AppendTag(requestBytes, 2, protowire.BytesType) // embedded message is same as a bytes field, in wire view.
requestBytes = protowire.AppendBytes(requestBytes, messageBytes)
return requestBytes, nil
}
Tell the field number, field type and the bytes, That's all. Commom way is like that.
func commonImplementation(messageBytes []byte, name string) (requestBytes []byte, err error) {
// receive it from file or network, not important.
var message protodef.HugeMessage
_ = proto.Unmarshal(messageBytes, &message) // slow
request := protodef.Request{
Name: name,
Payload: &message,
}
return proto.Marshal(&request) // slow
}
Some benchmark.
$ go test -bench=a -benchtime 10s ./pkg/
goos: darwin
goarch: arm64
pkg: pbembedding/pkg
BenchmarkCommon-8 49 288026442 ns/op
BenchmarkEmbedding-8 201 176032133 ns/op
PASS
ok pbembedding/pkg 80.196s
package pkg
import (
"github.com/stretchr/testify/assert"
"golang.org/x/exp/rand"
"google.golang.org/protobuf/proto"
"pbembedding/pkg/protodef"
"testing"
)
var hugeMessageSample = receiveHugeMessageFromSomewhere()
func TestEquivalent(t *testing.T) {
requestBytes1, _ := commonImplementation(hugeMessageSample, "xxxx")
requestBytes2, _ := binaryEmbeddingImplementation(hugeMessageSample, "xxxx")
// They are not always equal int bytes. you should compare them in message view instead of binary from
// due to: https://developers.google.com/protocol-buffers/docs/encoding#implications
// I'm Lazy.
assert.NotEmpty(t, requestBytes1)
assert.Equal(t, requestBytes1, requestBytes2)
var request protodef.Request
err := proto.Unmarshal(requestBytes1, &request)
assert.NoError(t, err)
assert.Equal(t, "xxxx", request.Name)
}
// actually mock one.
func receiveHugeMessageFromSomewhere() []byte {
buffer := make([]byte, 1024*1024*1024)
_, _ = rand.Read(buffer)
message := protodef.HugeMessage{
Data: buffer,
}
res, _ := proto.Marshal(&message)
return res
}
func BenchmarkCommon(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := commonImplementation(hugeMessageSample, "xxxx")
if err != nil {
panic(err)
}
}
}
func BenchmarkEmbedding(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := binaryEmbeddingImplementation(hugeMessageSample, "xxxx")
if err != nil {
panic(err)
}
}
}