| // 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()) | 
 | } |