bench_test.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. package bench_test
  5. import (
  6. "bytes"
  7. "flag"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "os/exec"
  12. "path/filepath"
  13. "strings"
  14. "testing"
  15. "time"
  16. "github.com/golang/protobuf/jsonpb"
  17. protoV1 "github.com/golang/protobuf/proto"
  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. benchpb "google.golang.org/protobuf/internal/testprotos/benchmarks"
  24. _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto2"
  25. _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message1/proto3"
  26. _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message2"
  27. _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message3"
  28. _ "google.golang.org/protobuf/internal/testprotos/benchmarks/datasets/google_message4"
  29. )
  30. var (
  31. benchV1 = flag.Bool("v1", false, "benchmark the v1 implementation")
  32. )
  33. func BenchmarkWire(b *testing.B) {
  34. bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
  35. for pb.Next() {
  36. for _, p := range ds.wire {
  37. m := ds.messageType.New().Interface()
  38. if err := Unmarshal(p, m); err != nil {
  39. b.Fatal(err)
  40. }
  41. }
  42. }
  43. })
  44. bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
  45. for pb.Next() {
  46. for _, m := range ds.messages {
  47. if _, err := Marshal(m); err != nil {
  48. b.Fatal(err)
  49. }
  50. }
  51. }
  52. })
  53. bench(b, "Size", func(ds dataset, pb *testing.PB) {
  54. for pb.Next() {
  55. for _, m := range ds.messages {
  56. Size(m)
  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 := UnmarshalText(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 := MarshalText(m); err != nil {
  76. b.Fatal(err)
  77. }
  78. }
  79. }
  80. })
  81. }
  82. func BenchmarkJSON(b *testing.B) {
  83. bench(b, "Unmarshal", func(ds dataset, pb *testing.PB) {
  84. for pb.Next() {
  85. for _, p := range ds.json {
  86. m := ds.messageType.New().Interface()
  87. if err := UnmarshalJSON(p, m); err != nil {
  88. b.Fatal(err)
  89. }
  90. }
  91. }
  92. })
  93. bench(b, "Marshal", func(ds dataset, pb *testing.PB) {
  94. for pb.Next() {
  95. for _, m := range ds.messages {
  96. if _, err := MarshalJSON(m); err != nil {
  97. b.Fatal(err)
  98. }
  99. }
  100. }
  101. })
  102. }
  103. func bench(b *testing.B, name string, f func(dataset, *testing.PB)) {
  104. b.Helper()
  105. b.Run(name, func(b *testing.B) {
  106. for _, ds := range datasets {
  107. b.Run(ds.name, func(b *testing.B) {
  108. b.RunParallel(func(pb *testing.PB) {
  109. f(ds, pb)
  110. })
  111. })
  112. }
  113. })
  114. }
  115. type dataset struct {
  116. name string
  117. messageType pref.MessageType
  118. messages []proto.Message
  119. wire [][]byte
  120. text [][]byte
  121. json [][]byte
  122. }
  123. var datasets []dataset
  124. func TestMain(m *testing.M) {
  125. // Load benchmark data early, to avoid including this step in -cpuprofile/-memprofile.
  126. //
  127. // For the larger benchmark datasets (not downloaded by default), preparing
  128. // this data is quite expensive. In addition, keeping the unmarshaled messages
  129. // in memory makes GC scans a substantial fraction of runtime CPU cost.
  130. //
  131. // It would be nice to avoid loading the data we aren't going to use. Unfortunately,
  132. // there isn't any simple way to tell what benchmarks are going to run; we can examine
  133. // the -test.bench flag, but parsing it is quite complicated.
  134. flag.Parse()
  135. if v := flag.Lookup("test.bench").Value.(flag.Getter).Get(); v == "" {
  136. // Don't bother loading data if we aren't going to run any benchmarks.
  137. // Avoids slowing down go test ./...
  138. return
  139. }
  140. if v := flag.Lookup("test.timeout").Value.(flag.Getter).Get().(time.Duration); v != 0 && v <= 10*time.Minute {
  141. // The default test timeout of 10m is too short if running all the benchmarks.
  142. // It's quite frustrating to discover this 10m through a benchmark run, so
  143. // catch the condition.
  144. //
  145. // The -timeout and -test.timeout flags are handled by the go command, which
  146. // forwards them along to the test binary, so we can't just set the default
  147. // to something reasonable; the go command will override it with its default.
  148. // We also can't ignore the timeout, because the go command kills a test which
  149. // runs more than a minute past its deadline.
  150. fmt.Fprintf(os.Stderr, "Test timeout of %v is probably too short; set -test.timeout=0.\n", v)
  151. os.Exit(1)
  152. }
  153. out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
  154. if err != nil {
  155. panic(err)
  156. }
  157. repoRoot := strings.TrimSpace(string(out))
  158. dataDir := filepath.Join(repoRoot, ".cache", "benchdata")
  159. filepath.Walk(dataDir, func(path string, _ os.FileInfo, _ error) error {
  160. if filepath.Ext(path) != ".pb" {
  161. return nil
  162. }
  163. raw, err := ioutil.ReadFile(path)
  164. if err != nil {
  165. panic(err)
  166. }
  167. dspb := &benchpb.BenchmarkDataset{}
  168. if err := proto.Unmarshal(raw, dspb); err != nil {
  169. panic(err)
  170. }
  171. mt, err := preg.GlobalTypes.FindMessageByName(pref.FullName(dspb.MessageName))
  172. if err != nil {
  173. panic(err)
  174. }
  175. ds := dataset{
  176. name: dspb.Name,
  177. messageType: mt,
  178. wire: dspb.Payload,
  179. }
  180. for _, payload := range dspb.Payload {
  181. m := mt.New().Interface()
  182. if err := proto.Unmarshal(payload, m); err != nil {
  183. panic(err)
  184. }
  185. ds.messages = append(ds.messages, m)
  186. b, err := prototext.Marshal(m)
  187. if err != nil {
  188. panic(err)
  189. }
  190. ds.text = append(ds.text, b)
  191. b, err = protojson.Marshal(m)
  192. if err != nil {
  193. panic(err)
  194. }
  195. ds.json = append(ds.json, b)
  196. }
  197. datasets = append(datasets, ds)
  198. return nil
  199. })
  200. os.Exit(m.Run())
  201. }
  202. func Unmarshal(b []byte, m proto.Message) error {
  203. if *benchV1 {
  204. return protoV1.Unmarshal(b, m.(protoV1.Message))
  205. }
  206. return proto.Unmarshal(b, m)
  207. }
  208. func Marshal(m proto.Message) ([]byte, error) {
  209. if *benchV1 {
  210. return protoV1.Marshal(m.(protoV1.Message))
  211. }
  212. return proto.Marshal(m)
  213. }
  214. func Size(m proto.Message) int {
  215. if *benchV1 {
  216. return protoV1.Size(m.(protoV1.Message))
  217. }
  218. return proto.Size(m)
  219. }
  220. func UnmarshalText(b []byte, m proto.Message) error {
  221. if *benchV1 {
  222. // Extra string conversion makes this not quite right.
  223. return protoV1.UnmarshalText(string(b), m.(protoV1.Message))
  224. }
  225. return prototext.Unmarshal(b, m)
  226. }
  227. func MarshalText(m proto.Message) ([]byte, error) {
  228. if *benchV1 {
  229. var b bytes.Buffer
  230. err := protoV1.MarshalText(&b, m.(protoV1.Message))
  231. return b.Bytes(), err
  232. }
  233. return prototext.Marshal(m)
  234. }
  235. func UnmarshalJSON(b []byte, m proto.Message) error {
  236. if *benchV1 {
  237. return jsonpb.Unmarshal(bytes.NewBuffer(b), m.(protoV1.Message))
  238. }
  239. return protojson.Unmarshal(b, m)
  240. }
  241. func MarshalJSON(m proto.Message) ([]byte, error) {
  242. if *benchV1 {
  243. var b bytes.Buffer
  244. err := (&jsonpb.Marshaler{}).Marshal(&b, m.(protoV1.Message))
  245. return b.Bytes(), err
  246. }
  247. return protojson.Marshal(m)
  248. }