xref: /aosp_15_r20/external/golang-protobuf/internal/benchmarks/bench_test.go (revision 1c12ee1efe575feb122dbf939ff15148a3b3e8f2)
1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package bench_test
6
7import (
8	"flag"
9	"fmt"
10	"io/ioutil"
11	"os"
12	"os/exec"
13	"path/filepath"
14	"strings"
15	"testing"
16	"time"
17
18	"google.golang.org/protobuf/encoding/protojson"
19	"google.golang.org/protobuf/encoding/prototext"
20	"google.golang.org/protobuf/proto"
21	"google.golang.org/protobuf/reflect/protoreflect"
22	"google.golang.org/protobuf/reflect/protoregistry"
23
24	benchpb "google.golang.org/protobuf/internal/testprotos/benchmarks"
25	_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto2"
26	_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto3"
27	_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message2"
28	_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message3"
29	_ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message4"
30)
31
32func BenchmarkWire(b *testing.B) {
33	bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
34		for pb.Next() {
35			for _, p := range ds.wire {
36				m := ds.messageType.New().Interface()
37				if err := proto.Unmarshal(p, m); err != nil {
38					b.Fatal(err)
39				}
40			}
41		}
42	})
43	bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
44		for pb.Next() {
45			for _, m := range ds.messages {
46				if _, err := proto.Marshal(m); err != nil {
47					b.Fatal(err)
48				}
49			}
50		}
51	})
52	bench(b, "Size", func(ds dataset, pb *testing.PB) {
53		for pb.Next() {
54			for _, m := range ds.messages {
55				proto.Size(m)
56			}
57		}
58	})
59}
60
61func BenchmarkText(b *testing.B) {
62	bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
63		for pb.Next() {
64			for _, p := range ds.text {
65				m := ds.messageType.New().Interface()
66				if err := prototext.Unmarshal(p, m); err != nil {
67					b.Fatal(err)
68				}
69			}
70		}
71	})
72	bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
73		for pb.Next() {
74			for _, m := range ds.messages {
75				if _, err := prototext.Marshal(m); err != nil {
76					b.Fatal(err)
77				}
78			}
79		}
80	})
81}
82
83func BenchmarkJSON(b *testing.B) {
84	bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
85		for pb.Next() {
86			for _, p := range ds.json {
87				m := ds.messageType.New().Interface()
88				if err := protojson.Unmarshal(p, m); err != nil {
89					b.Fatal(err)
90				}
91			}
92		}
93	})
94	bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
95		for pb.Next() {
96			for _, m := range ds.messages {
97				if _, err := protojson.Marshal(m); err != nil {
98					b.Fatal(err)
99				}
100			}
101		}
102	})
103}
104
105func Benchmark(b *testing.B) {
106	bench(b, "Clone", func(ds dataset, pb *testing.PB) {
107		for pb.Next() {
108			for _, src := range ds.messages {
109				proto.Clone(src)
110			}
111		}
112	})
113}
114
115func bench(b *testing.B, name string, f func(dataset, *testing.PB)) {
116	b.Helper()
117	b.Run(name, func(b *testing.B) {
118		for _, ds := range datasets {
119			b.Run(ds.name, func(b *testing.B) {
120				b.RunParallel(func(pb *testing.PB) {
121					f(ds, pb)
122				})
123			})
124		}
125	})
126}
127
128type dataset struct {
129	name        string
130	messageType protoreflect.MessageType
131	messages    []proto.Message
132	wire        [][]byte
133	text        [][]byte
134	json        [][]byte
135}
136
137var datasets []dataset
138
139func TestMain(m *testing.M) {
140	// Load benchmark data early, to avoid including this step in -cpuprofile/-memprofile.
141	//
142	// For the larger benchmark datasets (not downloaded by default), preparing
143	// this data is quite expensive. In addition, keeping the unmarshaled messages
144	// in memory makes GC scans a substantial fraction of runtime CPU cost.
145	//
146	// It would be nice to avoid loading the data we aren't going to use. Unfortunately,
147	// there isn't any simple way to tell what benchmarks are going to run; we can examine
148	// the -test.bench flag, but parsing it is quite complicated.
149	flag.Parse()
150	if v := flag.Lookup("test.bench").Value.(flag.Getter).Get(); v == "" {
151		// Don't bother loading data if we aren't going to run any benchmarks.
152		// Avoids slowing down go test ./...
153		return
154	}
155	if v := flag.Lookup("test.timeout").Value.(flag.Getter).Get().(time.Duration); v != 0 && v <= 10*time.Minute {
156		// The default test timeout of 10m is too short if running all the benchmarks.
157		// It's quite frustrating to discover this 10m through a benchmark run, so
158		// catch the condition.
159		//
160		// The -timeout and -test.timeout flags are handled by the go command, which
161		// forwards them along to the test binary, so we can't just set the default
162		// to something reasonable; the go command will override it with its default.
163		// We also can't ignore the timeout, because the go command kills a test which
164		// runs more than a minute past its deadline.
165		fmt.Fprintf(os.Stderr, "Test timeout of %v is probably too short; set -test.timeout=0.\n", v)
166		os.Exit(1)
167	}
168	out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
169	if err != nil {
170		panic(err)
171	}
172	repoRoot := strings.TrimSpace(string(out))
173	dataDir := filepath.Join(repoRoot, ".cache", "benchdata")
174	filepath.Walk(dataDir, func(path string, _ os.FileInfo, _ error) error {
175		if filepath.Ext(path) != ".pb" {
176			return nil
177		}
178		raw, err := ioutil.ReadFile(path)
179		if err != nil {
180			panic(err)
181		}
182		dspb := &benchpb.BenchmarkDataset{}
183		if err := proto.Unmarshal(raw, dspb); err != nil {
184			panic(err)
185		}
186		mt, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(dspb.MessageName))
187		if err != nil {
188			panic(err)
189		}
190		ds := dataset{
191			name:        dspb.Name,
192			messageType: mt,
193			wire:        dspb.Payload,
194		}
195		for _, payload := range dspb.Payload {
196			m := mt.New().Interface()
197			if err := proto.Unmarshal(payload, m); err != nil {
198				panic(err)
199			}
200			ds.messages = append(ds.messages, m)
201			b, err := prototext.Marshal(m)
202			if err != nil {
203				panic(err)
204			}
205			ds.text = append(ds.text, b)
206			b, err = protojson.Marshal(m)
207			if err != nil {
208				panic(err)
209			}
210			ds.json = append(ds.json, b)
211		}
212		datasets = append(datasets, ds)
213		return nil
214	})
215	os.Exit(m.Run())
216}
217