blob: 9335dbd227b63725b97b404dc6e9ae40491611e5 [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 (
Damien Neil42cfff42019-06-21 10:42:31 -07008 "flag"
9 "fmt"
10 "io/ioutil"
11 "os"
12 "os/exec"
13 "path/filepath"
14 "strings"
15 "testing"
16 "time"
17
Damien Neil42cfff42019-06-21 10:42:31 -070018 "google.golang.org/protobuf/encoding/protojson"
19 "google.golang.org/protobuf/encoding/prototext"
20 "google.golang.org/protobuf/proto"
21 pref "google.golang.org/protobuf/reflect/protoreflect"
22 preg "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
Damien Neil42cfff42019-06-21 10:42:31 -070032func 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()
Damien Neilec00e322020-01-09 09:23:52 -080037 if err := proto.Unmarshal(p, m); err != nil {
Damien Neil42cfff42019-06-21 10:42:31 -070038 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 {
Damien Neilec00e322020-01-09 09:23:52 -080046 if _, err := proto.Marshal(m); err != nil {
Damien Neil42cfff42019-06-21 10:42:31 -070047 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 {
Damien Neilec00e322020-01-09 09:23:52 -080055 proto.Size(m)
Damien Neil42cfff42019-06-21 10:42:31 -070056 }
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()
Damien Neilec00e322020-01-09 09:23:52 -080066 if err := prototext.Unmarshal(p, m); err != nil {
Damien Neil42cfff42019-06-21 10:42:31 -070067 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 {
Damien Neilec00e322020-01-09 09:23:52 -080075 if _, err := prototext.Marshal(m); err != nil {
Damien Neil42cfff42019-06-21 10:42:31 -070076 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()
Damien Neilec00e322020-01-09 09:23:52 -080088 if err := protojson.Unmarshal(p, m); err != nil {
Damien Neil42cfff42019-06-21 10:42:31 -070089 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 {
Damien Neilec00e322020-01-09 09:23:52 -080097 if _, err := protojson.Marshal(m); err != nil {
Damien Neil42cfff42019-06-21 10:42:31 -070098 b.Fatal(err)
99 }
100 }
101 }
102 })
103}
104
105func bench(b *testing.B, name string, f func(dataset, *testing.PB)) {
106 b.Helper()
107 b.Run(name, func(b *testing.B) {
108 for _, ds := range datasets {
109 b.Run(ds.name, func(b *testing.B) {
110 b.RunParallel(func(pb *testing.PB) {
111 f(ds, pb)
112 })
113 })
114 }
115 })
116}
117
118type dataset struct {
119 name string
120 messageType pref.MessageType
121 messages []proto.Message
122 wire [][]byte
123 text [][]byte
124 json [][]byte
125}
126
127var datasets []dataset
128
129func TestMain(m *testing.M) {
130 // Load benchmark data early, to avoid including this step in -cpuprofile/-memprofile.
131 //
132 // For the larger benchmark datasets (not downloaded by default), preparing
133 // this data is quite expensive. In addition, keeping the unmarshaled messages
134 // in memory makes GC scans a substantial fraction of runtime CPU cost.
135 //
136 // It would be nice to avoid loading the data we aren't going to use. Unfortunately,
137 // there isn't any simple way to tell what benchmarks are going to run; we can examine
138 // the -test.bench flag, but parsing it is quite complicated.
139 flag.Parse()
140 if v := flag.Lookup("test.bench").Value.(flag.Getter).Get(); v == "" {
141 // Don't bother loading data if we aren't going to run any benchmarks.
142 // Avoids slowing down go test ./...
143 return
144 }
145 if v := flag.Lookup("test.timeout").Value.(flag.Getter).Get().(time.Duration); v != 0 && v <= 10*time.Minute {
146 // The default test timeout of 10m is too short if running all the benchmarks.
147 // It's quite frustrating to discover this 10m through a benchmark run, so
148 // catch the condition.
149 //
150 // The -timeout and -test.timeout flags are handled by the go command, which
151 // forwards them along to the test binary, so we can't just set the default
152 // to something reasonable; the go command will override it with its default.
153 // We also can't ignore the timeout, because the go command kills a test which
154 // runs more than a minute past its deadline.
155 fmt.Fprintf(os.Stderr, "Test timeout of %v is probably too short; set -test.timeout=0.\n", v)
156 os.Exit(1)
157 }
158 out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
159 if err != nil {
160 panic(err)
161 }
162 repoRoot := strings.TrimSpace(string(out))
163 dataDir := filepath.Join(repoRoot, ".cache", "benchdata")
164 filepath.Walk(dataDir, func(path string, _ os.FileInfo, _ error) error {
165 if filepath.Ext(path) != ".pb" {
166 return nil
167 }
168 raw, err := ioutil.ReadFile(path)
169 if err != nil {
170 panic(err)
171 }
172 dspb := &benchpb.BenchmarkDataset{}
173 if err := proto.Unmarshal(raw, dspb); err != nil {
174 panic(err)
175 }
176 mt, err := preg.GlobalTypes.FindMessageByName(pref.FullName(dspb.MessageName))
177 if err != nil {
178 panic(err)
179 }
180 ds := dataset{
181 name: dspb.Name,
182 messageType: mt,
183 wire: dspb.Payload,
184 }
185 for _, payload := range dspb.Payload {
186 m := mt.New().Interface()
187 if err := proto.Unmarshal(payload, m); err != nil {
188 panic(err)
189 }
190 ds.messages = append(ds.messages, m)
191 b, err := prototext.Marshal(m)
192 if err != nil {
193 panic(err)
194 }
195 ds.text = append(ds.text, b)
196 b, err = protojson.Marshal(m)
197 if err != nil {
198 panic(err)
199 }
200 ds.json = append(ds.json, b)
201 }
202 datasets = append(datasets, ds)
203 return nil
204 })
205 os.Exit(m.Run())
206}