1// Go support for Protocol Buffers - Google's data interchange format
2//
3// Copyright 2010 The Go Authors.  All rights reserved.
4// https://github.com/golang/protobuf
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10//     * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12//     * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16//     * Neither the name of Google Inc. nor the names of its
17// contributors may be used to endorse or promote products derived from
18// this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32// +build go1.7
33
34package proto_test
35
36import (
37	"fmt"
38	"testing"
39
40	"github.com/golang/protobuf/proto"
41	tpb "github.com/golang/protobuf/proto/proto3_proto"
42)
43
44var msgBlackhole = new(tpb.Message)
45
46// BenchmarkVarint32ArraySmall shows the performance on an array of small int32 fields (1 and
47// 2 bytes long).
48func BenchmarkVarint32ArraySmall(b *testing.B) {
49	for i := uint(1); i <= 10; i++ {
50		dist := genInt32Dist([7]int{0, 3, 1}, 1<<i)
51		raw, err := proto.Marshal(&tpb.Message{
52			ShortKey: dist,
53		})
54		if err != nil {
55			b.Error("wrong encode", err)
56		}
57		b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
58			scratchBuf := proto.NewBuffer(nil)
59			b.ResetTimer()
60			for k := 0; k < b.N; k++ {
61				scratchBuf.SetBuf(raw)
62				msgBlackhole.Reset()
63				if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
64					b.Error("wrong decode", err)
65				}
66			}
67		})
68	}
69}
70
71// BenchmarkVarint32ArrayLarge shows the performance on an array of large int32 fields (3 and
72// 4 bytes long, with a small number of 1, 2, 5 and 10 byte long versions).
73func BenchmarkVarint32ArrayLarge(b *testing.B) {
74	for i := uint(1); i <= 10; i++ {
75		dist := genInt32Dist([7]int{0, 1, 2, 4, 8, 1, 1}, 1<<i)
76		raw, err := proto.Marshal(&tpb.Message{
77			ShortKey: dist,
78		})
79		if err != nil {
80			b.Error("wrong encode", err)
81		}
82		b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
83			scratchBuf := proto.NewBuffer(nil)
84			b.ResetTimer()
85			for k := 0; k < b.N; k++ {
86				scratchBuf.SetBuf(raw)
87				msgBlackhole.Reset()
88				if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
89					b.Error("wrong decode", err)
90				}
91			}
92		})
93	}
94}
95
96// BenchmarkVarint64ArraySmall shows the performance on an array of small int64 fields (1 and
97// 2 bytes long).
98func BenchmarkVarint64ArraySmall(b *testing.B) {
99	for i := uint(1); i <= 10; i++ {
100		dist := genUint64Dist([11]int{0, 3, 1}, 1<<i)
101		raw, err := proto.Marshal(&tpb.Message{
102			Key: dist,
103		})
104		if err != nil {
105			b.Error("wrong encode", err)
106		}
107		b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
108			scratchBuf := proto.NewBuffer(nil)
109			b.ResetTimer()
110			for k := 0; k < b.N; k++ {
111				scratchBuf.SetBuf(raw)
112				msgBlackhole.Reset()
113				if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
114					b.Error("wrong decode", err)
115				}
116			}
117		})
118	}
119}
120
121// BenchmarkVarint64ArrayLarge shows the performance on an array of large int64 fields (6, 7,
122// and 8 bytes long with a small number of the other sizes).
123func BenchmarkVarint64ArrayLarge(b *testing.B) {
124	for i := uint(1); i <= 10; i++ {
125		dist := genUint64Dist([11]int{0, 1, 1, 2, 4, 8, 16, 32, 16, 1, 1}, 1<<i)
126		raw, err := proto.Marshal(&tpb.Message{
127			Key: dist,
128		})
129		if err != nil {
130			b.Error("wrong encode", err)
131		}
132		b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
133			scratchBuf := proto.NewBuffer(nil)
134			b.ResetTimer()
135			for k := 0; k < b.N; k++ {
136				scratchBuf.SetBuf(raw)
137				msgBlackhole.Reset()
138				if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
139					b.Error("wrong decode", err)
140				}
141			}
142		})
143	}
144}
145
146// BenchmarkVarint64ArrayMixed shows the performance of lots of small messages, each
147// containing a small number of large (3, 4, and 5 byte) repeated int64s.
148func BenchmarkVarint64ArrayMixed(b *testing.B) {
149	for i := uint(1); i <= 1<<5; i <<= 1 {
150		dist := genUint64Dist([11]int{0, 0, 0, 4, 6, 4, 0, 0, 0, 0, 0}, int(i))
151		// number of sub fields
152		for k := uint(1); k <= 1<<10; k <<= 2 {
153			msg := &tpb.Message{}
154			for m := uint(0); m < k; m++ {
155				msg.Children = append(msg.Children, &tpb.Message{
156					Key: dist,
157				})
158			}
159			raw, err := proto.Marshal(msg)
160			if err != nil {
161				b.Error("wrong encode", err)
162			}
163			b.Run(fmt.Sprintf("Fields%vLen%v", k, i), func(b *testing.B) {
164				scratchBuf := proto.NewBuffer(nil)
165				b.ResetTimer()
166				for k := 0; k < b.N; k++ {
167					scratchBuf.SetBuf(raw)
168					msgBlackhole.Reset()
169					if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
170						b.Error("wrong decode", err)
171					}
172				}
173			})
174		}
175	}
176}
177
178// genInt32Dist generates a slice of ints that will match the size distribution of dist.
179// A size of 6 corresponds to a max length varint32, which is 10 bytes.  The distribution
180// is 1-indexed. (i.e. the value at index 1 is how many 1 byte ints to create).
181func genInt32Dist(dist [7]int, count int) (dest []int32) {
182	for i := 0; i < count; i++ {
183		for k := 0; k < len(dist); k++ {
184			var num int32
185			switch k {
186			case 1:
187				num = 1<<7 - 1
188			case 2:
189				num = 1<<14 - 1
190			case 3:
191				num = 1<<21 - 1
192			case 4:
193				num = 1<<28 - 1
194			case 5:
195				num = 1<<29 - 1
196			case 6:
197				num = -1
198			}
199			for m := 0; m < dist[k]; m++ {
200				dest = append(dest, num)
201			}
202		}
203	}
204	return
205}
206
207// genUint64Dist generates a slice of ints that will match the size distribution of dist.
208// The distribution is 1-indexed. (i.e. the value at index 1 is how many 1 byte ints to create).
209func genUint64Dist(dist [11]int, count int) (dest []uint64) {
210	for i := 0; i < count; i++ {
211		for k := 0; k < len(dist); k++ {
212			var num uint64
213			switch k {
214			case 1:
215				num = 1<<7 - 1
216			case 2:
217				num = 1<<14 - 1
218			case 3:
219				num = 1<<21 - 1
220			case 4:
221				num = 1<<28 - 1
222			case 5:
223				num = 1<<35 - 1
224			case 6:
225				num = 1<<42 - 1
226			case 7:
227				num = 1<<49 - 1
228			case 8:
229				num = 1<<56 - 1
230			case 9:
231				num = 1<<63 - 1
232			case 10:
233				num = 1<<64 - 1
234			}
235			for m := 0; m < dist[k]; m++ {
236				dest = append(dest, num)
237			}
238		}
239	}
240	return
241}
242
243// BenchmarkDecodeEmpty measures the overhead of doing the minimal possible decode.
244func BenchmarkDecodeEmpty(b *testing.B) {
245	raw, err := proto.Marshal(&tpb.Message{})
246	if err != nil {
247		b.Error("wrong encode", err)
248	}
249	b.ResetTimer()
250	for i := 0; i < b.N; i++ {
251		if err := proto.Unmarshal(raw, msgBlackhole); err != nil {
252			b.Error("wrong decode", err)
253		}
254	}
255}
256