|  | // Copyright 2019 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package bench_test | 
|  |  | 
|  | import ( | 
|  | "flag" | 
|  | "fmt" | 
|  | "os" | 
|  | "os/exec" | 
|  | "path/filepath" | 
|  | "strings" | 
|  | "testing" | 
|  | "time" | 
|  |  | 
|  | "google.golang.org/protobuf/encoding/protojson" | 
|  | "google.golang.org/protobuf/encoding/prototext" | 
|  | "google.golang.org/protobuf/proto" | 
|  | "google.golang.org/protobuf/reflect/protoreflect" | 
|  | "google.golang.org/protobuf/reflect/protoregistry" | 
|  |  | 
|  | benchpb "google.golang.org/protobuf/internal/testprotos/benchmarks" | 
|  | _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto2" | 
|  | _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto3" | 
|  | _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message2" | 
|  | _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message3" | 
|  | _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message4" | 
|  | ) | 
|  |  | 
|  | func BenchmarkWire(b *testing.B) { | 
|  | bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, p := range ds.wire { | 
|  | m := ds.messageType.New().Interface() | 
|  | if err := proto.Unmarshal(p, m); err != nil { | 
|  | b.Fatal(err) | 
|  | } | 
|  | } | 
|  | } | 
|  | }) | 
|  | bench(b, "Marshal", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, m := range ds.messages { | 
|  | if _, err := proto.Marshal(m); err != nil { | 
|  | b.Fatal(err) | 
|  | } | 
|  | } | 
|  | } | 
|  | }) | 
|  | bench(b, "Size", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, m := range ds.messages { | 
|  | proto.Size(m) | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkText(b *testing.B) { | 
|  | bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, p := range ds.text { | 
|  | m := ds.messageType.New().Interface() | 
|  | if err := prototext.Unmarshal(p, m); err != nil { | 
|  | b.Fatal(err) | 
|  | } | 
|  | } | 
|  | } | 
|  | }) | 
|  | bench(b, "Marshal", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, m := range ds.messages { | 
|  | if _, err := prototext.Marshal(m); err != nil { | 
|  | b.Fatal(err) | 
|  | } | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkJSON(b *testing.B) { | 
|  | bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, p := range ds.json { | 
|  | m := ds.messageType.New().Interface() | 
|  | if err := protojson.Unmarshal(p, m); err != nil { | 
|  | b.Fatal(err) | 
|  | } | 
|  | } | 
|  | } | 
|  | }) | 
|  | bench(b, "Marshal", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, m := range ds.messages { | 
|  | if _, err := protojson.Marshal(m); err != nil { | 
|  | b.Fatal(err) | 
|  | } | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func Benchmark(b *testing.B) { | 
|  | bench(b, "Clone", func(ds dataset, pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | for _, src := range ds.messages { | 
|  | proto.Clone(src) | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func bench(b *testing.B, name string, f func(dataset, *testing.PB)) { | 
|  | b.Helper() | 
|  | b.Run(name, func(b *testing.B) { | 
|  | for _, ds := range datasets { | 
|  | b.Run(ds.name, func(b *testing.B) { | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | f(ds, pb) | 
|  | }) | 
|  | }) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | type dataset struct { | 
|  | name        string | 
|  | messageType protoreflect.MessageType | 
|  | messages    []proto.Message | 
|  | wire        [][]byte | 
|  | text        [][]byte | 
|  | json        [][]byte | 
|  | } | 
|  |  | 
|  | var datasets []dataset | 
|  |  | 
|  | func TestMain(m *testing.M) { | 
|  | // Load benchmark data early, to avoid including this step in -cpuprofile/-memprofile. | 
|  | // | 
|  | // For the larger benchmark datasets (not downloaded by default), preparing | 
|  | // this data is quite expensive. In addition, keeping the unmarshaled messages | 
|  | // in memory makes GC scans a substantial fraction of runtime CPU cost. | 
|  | // | 
|  | // It would be nice to avoid loading the data we aren't going to use. Unfortunately, | 
|  | // there isn't any simple way to tell what benchmarks are going to run; we can examine | 
|  | // the -test.bench flag, but parsing it is quite complicated. | 
|  | flag.Parse() | 
|  | if v := flag.Lookup("test.bench").Value.(flag.Getter).Get(); v == "" { | 
|  | // Don't bother loading data if we aren't going to run any benchmarks. | 
|  | // Avoids slowing down go test ./... | 
|  | return | 
|  | } | 
|  | if v := flag.Lookup("test.timeout").Value.(flag.Getter).Get().(time.Duration); v != 0 && v <= 10*time.Minute { | 
|  | // The default test timeout of 10m is too short if running all the benchmarks. | 
|  | // It's quite frustrating to discover this 10m through a benchmark run, so | 
|  | // catch the condition. | 
|  | // | 
|  | // The -timeout and -test.timeout flags are handled by the go command, which | 
|  | // forwards them along to the test binary, so we can't just set the default | 
|  | // to something reasonable; the go command will override it with its default. | 
|  | // We also can't ignore the timeout, because the go command kills a test which | 
|  | // runs more than a minute past its deadline. | 
|  | fmt.Fprintf(os.Stderr, "Test timeout of %v is probably too short; set -test.timeout=0.\n", v) | 
|  | os.Exit(1) | 
|  | } | 
|  | out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput() | 
|  | if err != nil { | 
|  | panic(err) | 
|  | } | 
|  | repoRoot := strings.TrimSpace(string(out)) | 
|  | dataDir := filepath.Join(repoRoot, ".cache", "benchdata") | 
|  | filepath.Walk(dataDir, func(path string, _ os.FileInfo, _ error) error { | 
|  | if filepath.Ext(path) != ".pb" { | 
|  | return nil | 
|  | } | 
|  | raw, err := os.ReadFile(path) | 
|  | if err != nil { | 
|  | panic(err) | 
|  | } | 
|  | dspb := &benchpb.BenchmarkDataset{} | 
|  | if err := proto.Unmarshal(raw, dspb); err != nil { | 
|  | panic(err) | 
|  | } | 
|  | mt, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(dspb.MessageName)) | 
|  | if err != nil { | 
|  | panic(err) | 
|  | } | 
|  | ds := dataset{ | 
|  | name:        dspb.Name, | 
|  | messageType: mt, | 
|  | wire:        dspb.Payload, | 
|  | } | 
|  | for _, payload := range dspb.Payload { | 
|  | m := mt.New().Interface() | 
|  | if err := proto.Unmarshal(payload, m); err != nil { | 
|  | panic(err) | 
|  | } | 
|  | ds.messages = append(ds.messages, m) | 
|  | b, err := prototext.Marshal(m) | 
|  | if err != nil { | 
|  | panic(err) | 
|  | } | 
|  | ds.text = append(ds.text, b) | 
|  | b, err = protojson.Marshal(m) | 
|  | if err != nil { | 
|  | panic(err) | 
|  | } | 
|  | ds.json = append(ds.json, b) | 
|  | } | 
|  | datasets = append(datasets, ds) | 
|  | return nil | 
|  | }) | 
|  | os.Exit(m.Run()) | 
|  | } |