Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 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 | |
| 5 | package bench_test |
| 6 | |
| 7 | import ( |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 8 | "flag" |
| 9 | "fmt" |
| 10 | "io/ioutil" |
| 11 | "os" |
| 12 | "os/exec" |
| 13 | "path/filepath" |
| 14 | "strings" |
| 15 | "testing" |
| 16 | "time" |
| 17 | |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 18 | "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 Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 32 | func 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 Neil | ec00e32 | 2020-01-09 09:23:52 -0800 | [diff] [blame^] | 37 | if err := proto.Unmarshal(p, m); err != nil { |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 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 { |
Damien Neil | ec00e32 | 2020-01-09 09:23:52 -0800 | [diff] [blame^] | 46 | if _, err := proto.Marshal(m); err != nil { |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 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 { |
Damien Neil | ec00e32 | 2020-01-09 09:23:52 -0800 | [diff] [blame^] | 55 | proto.Size(m) |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 56 | } |
| 57 | } |
| 58 | }) |
| 59 | } |
| 60 | |
| 61 | func 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 Neil | ec00e32 | 2020-01-09 09:23:52 -0800 | [diff] [blame^] | 66 | if err := prototext.Unmarshal(p, m); err != nil { |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 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 { |
Damien Neil | ec00e32 | 2020-01-09 09:23:52 -0800 | [diff] [blame^] | 75 | if _, err := prototext.Marshal(m); err != nil { |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 76 | b.Fatal(err) |
| 77 | } |
| 78 | } |
| 79 | } |
| 80 | }) |
| 81 | } |
| 82 | |
| 83 | func 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 Neil | ec00e32 | 2020-01-09 09:23:52 -0800 | [diff] [blame^] | 88 | if err := protojson.Unmarshal(p, m); err != nil { |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 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 { |
Damien Neil | ec00e32 | 2020-01-09 09:23:52 -0800 | [diff] [blame^] | 97 | if _, err := protojson.Marshal(m); err != nil { |
Damien Neil | 42cfff4 | 2019-06-21 10:42:31 -0700 | [diff] [blame] | 98 | b.Fatal(err) |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | }) |
| 103 | } |
| 104 | |
| 105 | func 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 | |
| 118 | type dataset struct { |
| 119 | name string |
| 120 | messageType pref.MessageType |
| 121 | messages []proto.Message |
| 122 | wire [][]byte |
| 123 | text [][]byte |
| 124 | json [][]byte |
| 125 | } |
| 126 | |
| 127 | var datasets []dataset |
| 128 | |
| 129 | func 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 | } |