...

Source file src/google.golang.org/protobuf/internal/benchmarks/bench_test.go

Documentation: google.golang.org/protobuf/internal/benchmarks

     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 (
     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  
    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()
    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  
    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()
    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  
    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()
    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  
   105  func 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  
   115  func 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  
   128  type dataset struct {
   129  	name        string
   130  	messageType protoreflect.MessageType
   131  	messages    []proto.Message
   132  	wire        [][]byte
   133  	text        [][]byte
   134  	json        [][]byte
   135  }
   136  
   137  var datasets []dataset
   138  
   139  func 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  

View as plain text