protobuf-go/internal/benchmarks/bench_test.go
Damien Neil e8e8875f94 proto, runtime/protoiface, internal/impl: add fast-path Merge
Comparing -tags=protoreflect to fast-path:

name                              old time/op    new time/op    delta
pkg:google.golang.org/protobuf/internal/benchmarks goos:linux goarch:amd64
/Clone/google_message1_proto2-12    1.70µs ± 1%    0.30µs ± 1%  -82.64%  (p=0.001 n=7+7)
/Clone/google_message1_proto3-12    1.01µs ± 1%    0.19µs ± 1%  -80.77%  (p=0.000 n=7+8)
/Clone/google_message2-12            818µs ± 8%     141µs ± 6%  -82.78%  (p=0.000 n=8+8)
pkg:google.golang.org/protobuf/internal/benchmarks/micro goos:linux goarch:amd64
EmptyMessage/Clone-12               51.1ns ± 1%    39.3ns ± 3%  -23.03%  (p=0.000 n=7+8)
RepeatedInt32/Clone-12              24.5µs ± 1%     1.1µs ± 3%  -95.64%  (p=0.000 n=8+8)
Required/Clone-12                    978ns ± 1%     132ns ± 2%  -86.46%  (p=0.000 n=8+8)

name                              old alloc/op   new alloc/op   delta
pkg:google.golang.org/protobuf/internal/benchmarks goos:linux goarch:amd64
/Clone/google_message1_proto2-12    1.08kB ± 0%    0.74kB ± 0%  -31.85%  (p=0.000 n=8+8)
/Clone/google_message1_proto3-12      872B ± 0%      544B ± 0%  -37.61%  (p=0.000 n=8+8)
/Clone/google_message2-12            602kB ± 0%     411kB ± 0%  -31.65%  (p=0.000 n=8+8)
pkg:google.golang.org/protobuf/internal/benchmarks/micro goos:linux goarch:amd64
EmptyMessage/Clone-12                96.0B ± 0%     64.0B ± 0%  -33.33%  (p=0.000 n=8+8)
RepeatedInt32/Clone-12              25.4kB ± 0%     3.2kB ± 0%  -87.33%  (p=0.000 n=8+8)
Required/Clone-12                     416B ± 0%      256B ± 0%  -38.46%  (p=0.000 n=8+8)

name                              old allocs/op  new allocs/op  delta
pkg:google.golang.org/protobuf/internal/benchmarks goos:linux goarch:amd64
/Clone/google_message1_proto2-12      52.0 ± 0%      21.0 ± 0%  -59.62%  (p=0.000 n=8+8)
/Clone/google_message1_proto3-12      33.0 ± 0%       3.0 ± 0%  -90.91%  (p=0.000 n=8+8)
/Clone/google_message2-12            22.3k ± 0%      7.5k ± 0%  -66.41%  (p=0.000 n=8+8)
pkg:google.golang.org/protobuf/internal/benchmarks/micro goos:linux goarch:amd64
EmptyMessage/Clone-12                 3.00 ± 0%      2.00 ± 0%  -33.33%  (p=0.000 n=8+8)
RepeatedInt32/Clone-12               1.51k ± 0%     0.00k ± 0%  -99.80%  (p=0.000 n=8+8)
Required/Clone-12                     51.0 ± 0%      18.0 ± 0%  -64.71%  (p=0.000 n=8+8)

Change-Id: Ife9018097c34cb025dc9c4fdd9a61b2f947853c6
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/219147
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
2020-02-14 21:47:10 +00:00

217 lines
5.9 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 bench_test
import (
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
pref "google.golang.org/protobuf/reflect/protoreflect"
preg "google.golang.org/protobuf/reflect/protoregistry"
benchpb "google.golang.org/protobuf/internal/testprotos/benchmarks"
_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto2"
_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto3"
_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message2"
_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message3"
_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message4"
)
func BenchmarkWire(b *testing.B) {
bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, p := range ds.wire {
m := ds.messageType.New().Interface()
if err := proto.Unmarshal(p, m); err != nil {
b.Fatal(err)
}
}
}
})
bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, m := range ds.messages {
if _, err := proto.Marshal(m); err != nil {
b.Fatal(err)
}
}
}
})
bench(b, "Size", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, m := range ds.messages {
proto.Size(m)
}
}
})
}
func BenchmarkText(b *testing.B) {
bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, p := range ds.text {
m := ds.messageType.New().Interface()
if err := prototext.Unmarshal(p, m); err != nil {
b.Fatal(err)
}
}
}
})
bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, m := range ds.messages {
if _, err := prototext.Marshal(m); err != nil {
b.Fatal(err)
}
}
}
})
}
func BenchmarkJSON(b *testing.B) {
bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, p := range ds.json {
m := ds.messageType.New().Interface()
if err := protojson.Unmarshal(p, m); err != nil {
b.Fatal(err)
}
}
}
})
bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, m := range ds.messages {
if _, err := protojson.Marshal(m); err != nil {
b.Fatal(err)
}
}
}
})
}
func Benchmark(b *testing.B) {
bench(b, "Clone", func(ds dataset, pb *testing.PB) {
for pb.Next() {
for _, src := range ds.messages {
proto.Clone(src)
}
}
})
}
func bench(b *testing.B, name string, f func(dataset, *testing.PB)) {
b.Helper()
b.Run(name, func(b *testing.B) {
for _, ds := range datasets {
b.Run(ds.name, func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
f(ds, pb)
})
})
}
})
}
type dataset struct {
name string
messageType pref.MessageType
messages []proto.Message
wire [][]byte
text [][]byte
json [][]byte
}
var datasets []dataset
func TestMain(m *testing.M) {
// Load benchmark data early, to avoid including this step in -cpuprofile/-memprofile.
//
// For the larger benchmark datasets (not downloaded by default), preparing
// this data is quite expensive. In addition, keeping the unmarshaled messages
// in memory makes GC scans a substantial fraction of runtime CPU cost.
//
// It would be nice to avoid loading the data we aren't going to use. Unfortunately,
// there isn't any simple way to tell what benchmarks are going to run; we can examine
// the -test.bench flag, but parsing it is quite complicated.
flag.Parse()
if v := flag.Lookup("test.bench").Value.(flag.Getter).Get(); v == "" {
// Don't bother loading data if we aren't going to run any benchmarks.
// Avoids slowing down go test ./...
return
}
if v := flag.Lookup("test.timeout").Value.(flag.Getter).Get().(time.Duration); v != 0 && v <= 10*time.Minute {
// The default test timeout of 10m is too short if running all the benchmarks.
// It's quite frustrating to discover this 10m through a benchmark run, so
// catch the condition.
//
// The -timeout and -test.timeout flags are handled by the go command, which
// forwards them along to the test binary, so we can't just set the default
// to something reasonable; the go command will override it with its default.
// We also can't ignore the timeout, because the go command kills a test which
// runs more than a minute past its deadline.
fmt.Fprintf(os.Stderr, "Test timeout of %v is probably too short; set -test.timeout=0.\n", v)
os.Exit(1)
}
out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
if err != nil {
panic(err)
}
repoRoot := strings.TrimSpace(string(out))
dataDir := filepath.Join(repoRoot, ".cache", "benchdata")
filepath.Walk(dataDir, func(path string, _ os.FileInfo, _ error) error {
if filepath.Ext(path) != ".pb" {
return nil
}
raw, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
dspb := &benchpb.BenchmarkDataset{}
if err := proto.Unmarshal(raw, dspb); err != nil {
panic(err)
}
mt, err := preg.GlobalTypes.FindMessageByName(pref.FullName(dspb.MessageName))
if err != nil {
panic(err)
}
ds := dataset{
name: dspb.Name,
messageType: mt,
wire: dspb.Payload,
}
for _, payload := range dspb.Payload {
m := mt.New().Interface()
if err := proto.Unmarshal(payload, m); err != nil {
panic(err)
}
ds.messages = append(ds.messages, m)
b, err := prototext.Marshal(m)
if err != nil {
panic(err)
}
ds.text = append(ds.text, b)
b, err = protojson.Marshal(m)
if err != nil {
panic(err)
}
ds.json = append(ds.json, b)
}
datasets = append(datasets, ds)
return nil
})
os.Exit(m.Run())
}