blob: 25d4b309ab8639f48a527904671181dd6b353cfd [file] [log] [blame]
Damien Neil42cfff42019-06-21 10:42:31 -07001// 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 "bytes"
9 "flag"
10 "fmt"
11 "io/ioutil"
12 "os"
13 "os/exec"
14 "path/filepath"
15 "strings"
16 "testing"
17 "time"
18
19 "github.com/golang/protobuf/jsonpb"
20 protoV1 "github.com/golang/protobuf/proto"
21 "google.golang.org/protobuf/encoding/protojson"
22 "google.golang.org/protobuf/encoding/prototext"
23 "google.golang.org/protobuf/proto"
24 pref "google.golang.org/protobuf/reflect/protoreflect"
25 preg "google.golang.org/protobuf/reflect/protoregistry"
26
27 benchpb "google.golang.org/protobuf/internal/testprotos/benchmarks"
28 _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto2"
29 _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto3"
30 _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message2"
31 _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message3"
32 _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message4"
33)
34
35var (
36 benchV1 = flag.Bool("v1", false, "benchmark the v1 implementation")
37)
38
39func BenchmarkWire(b *testing.B) {
40 bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
41 for pb.Next() {
42 for _, p := range ds.wire {
43 m := ds.messageType.New().Interface()
44 if err := Unmarshal(p, m); err != nil {
45 b.Fatal(err)
46 }
47 }
48 }
49 })
50 bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
51 for pb.Next() {
52 for _, m := range ds.messages {
53 if _, err := Marshal(m); err != nil {
54 b.Fatal(err)
55 }
56 }
57 }
58 })
59 bench(b, "Size", func(ds dataset, pb *testing.PB) {
60 for pb.Next() {
61 for _, m := range ds.messages {
62 Size(m)
63 }
64 }
65 })
66}
67
68func BenchmarkText(b *testing.B) {
69 bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
70 for pb.Next() {
71 for _, p := range ds.text {
72 m := ds.messageType.New().Interface()
73 if err := UnmarshalText(p, m); err != nil {
74 b.Fatal(err)
75 }
76 }
77 }
78 })
79 bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
80 for pb.Next() {
81 for _, m := range ds.messages {
82 if _, err := MarshalText(m); err != nil {
83 b.Fatal(err)
84 }
85 }
86 }
87 })
88}
89
90func BenchmarkJSON(b *testing.B) {
91 bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
92 for pb.Next() {
93 for _, p := range ds.json {
94 m := ds.messageType.New().Interface()
95 if err := UnmarshalJSON(p, m); err != nil {
96 b.Fatal(err)
97 }
98 }
99 }
100 })
101 bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
102 for pb.Next() {
103 for _, m := range ds.messages {
104 if _, err := MarshalJSON(m); err != nil {
105 b.Fatal(err)
106 }
107 }
108 }
109 })
110}
111
112func bench(b *testing.B, name string, f func(dataset, *testing.PB)) {
113 b.Helper()
114 b.Run(name, func(b *testing.B) {
115 for _, ds := range datasets {
116 b.Run(ds.name, func(b *testing.B) {
117 b.RunParallel(func(pb *testing.PB) {
118 f(ds, pb)
119 })
120 })
121 }
122 })
123}
124
125type dataset struct {
126 name string
127 messageType pref.MessageType
128 messages []proto.Message
129 wire [][]byte
130 text [][]byte
131 json [][]byte
132}
133
134var datasets []dataset
135
136func TestMain(m *testing.M) {
137 // Load benchmark data early, to avoid including this step in -cpuprofile/-memprofile.
138 //
139 // For the larger benchmark datasets (not downloaded by default), preparing
140 // this data is quite expensive. In addition, keeping the unmarshaled messages
141 // in memory makes GC scans a substantial fraction of runtime CPU cost.
142 //
143 // It would be nice to avoid loading the data we aren't going to use. Unfortunately,
144 // there isn't any simple way to tell what benchmarks are going to run; we can examine
145 // the -test.bench flag, but parsing it is quite complicated.
146 flag.Parse()
147 if v := flag.Lookup("test.bench").Value.(flag.Getter).Get(); v == "" {
148 // Don't bother loading data if we aren't going to run any benchmarks.
149 // Avoids slowing down go test ./...
150 return
151 }
152 if v := flag.Lookup("test.timeout").Value.(flag.Getter).Get().(time.Duration); v != 0 && v <= 10*time.Minute {
153 // The default test timeout of 10m is too short if running all the benchmarks.
154 // It's quite frustrating to discover this 10m through a benchmark run, so
155 // catch the condition.
156 //
157 // The -timeout and -test.timeout flags are handled by the go command, which
158 // forwards them along to the test binary, so we can't just set the default
159 // to something reasonable; the go command will override it with its default.
160 // We also can't ignore the timeout, because the go command kills a test which
161 // runs more than a minute past its deadline.
162 fmt.Fprintf(os.Stderr, "Test timeout of %v is probably too short; set -test.timeout=0.\n", v)
163 os.Exit(1)
164 }
165 out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
166 if err != nil {
167 panic(err)
168 }
169 repoRoot := strings.TrimSpace(string(out))
170 dataDir := filepath.Join(repoRoot, ".cache", "benchdata")
171 filepath.Walk(dataDir, func(path string, _ os.FileInfo, _ error) error {
172 if filepath.Ext(path) != ".pb" {
173 return nil
174 }
175 raw, err := ioutil.ReadFile(path)
176 if err != nil {
177 panic(err)
178 }
179 dspb := &benchpb.BenchmarkDataset{}
180 if err := proto.Unmarshal(raw, dspb); err != nil {
181 panic(err)
182 }
183 mt, err := preg.GlobalTypes.FindMessageByName(pref.FullName(dspb.MessageName))
184 if err != nil {
185 panic(err)
186 }
187 ds := dataset{
188 name: dspb.Name,
189 messageType: mt,
190 wire: dspb.Payload,
191 }
192 for _, payload := range dspb.Payload {
193 m := mt.New().Interface()
194 if err := proto.Unmarshal(payload, m); err != nil {
195 panic(err)
196 }
197 ds.messages = append(ds.messages, m)
198 b, err := prototext.Marshal(m)
199 if err != nil {
200 panic(err)
201 }
202 ds.text = append(ds.text, b)
203 b, err = protojson.Marshal(m)
204 if err != nil {
205 panic(err)
206 }
207 ds.json = append(ds.json, b)
208 }
209 datasets = append(datasets, ds)
210 return nil
211 })
212 os.Exit(m.Run())
213}
214
215func Unmarshal(b []byte, m proto.Message) error {
216 if *benchV1 {
217 return protoV1.Unmarshal(b, m.(protoV1.Message))
218 }
219 return proto.Unmarshal(b, m)
220}
221
222func Marshal(m proto.Message) ([]byte, error) {
223 if *benchV1 {
224 return protoV1.Marshal(m.(protoV1.Message))
225 }
226 return proto.Marshal(m)
227}
228
229func Size(m proto.Message) int {
230 if *benchV1 {
231 return protoV1.Size(m.(protoV1.Message))
232 }
233 return proto.Size(m)
234}
235
236func UnmarshalText(b []byte, m proto.Message) error {
237 if *benchV1 {
238 // Extra string conversion makes this not quite right.
239 return protoV1.UnmarshalText(string(b), m.(protoV1.Message))
240 }
241 return prototext.Unmarshal(b, m)
242}
243
244func MarshalText(m proto.Message) ([]byte, error) {
245 if *benchV1 {
246 var b bytes.Buffer
247 err := protoV1.MarshalText(&b, m.(protoV1.Message))
248 return b.Bytes(), err
249 }
250 return prototext.Marshal(m)
251}
252
253func UnmarshalJSON(b []byte, m proto.Message) error {
254 if *benchV1 {
255 return jsonpb.Unmarshal(bytes.NewBuffer(b), m.(protoV1.Message))
256 }
257 return protojson.Unmarshal(b, m)
258}
259
260func MarshalJSON(m proto.Message) ([]byte, error) {
261 if *benchV1 {
262 var b bytes.Buffer
263 err := (&jsonpb.Marshaler{}).Marshal(&b, m.(protoV1.Message))
264 return b.Bytes(), err
265 }
266 return protojson.Marshal(m)
267}