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