1 // Copyright (c) 2009-2021, Google LLC
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above copyright
9 // notice, this list of conditions and the following disclaimer in the
10 // documentation and/or other materials provided with the distribution.
11 // * Neither the name of Google LLC nor the
12 // names of its contributors may be used to endorse or promote products
13 // derived from this software without specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 // ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
19 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26 #include <benchmark/benchmark.h>
27
28 #include <string.h>
29
30 #include "google/ads/googleads/v13/services/google_ads_service.upbdefs.h"
31 #include "google/protobuf/descriptor.pb.h"
32 #include "absl/container/flat_hash_set.h"
33 #include "google/protobuf/dynamic_message.h"
34 #include "benchmarks/descriptor.pb.h"
35 #include "benchmarks/descriptor.upb.h"
36 #include "benchmarks/descriptor.upbdefs.h"
37 #include "benchmarks/descriptor_sv.pb.h"
38 #include "upb/base/log2.h"
39 #include "upb/mem/arena.h"
40 #include "upb/reflection/def.hpp"
41
42 upb_StringView descriptor = benchmarks_descriptor_proto_upbdefinit.descriptor;
43 namespace protobuf = ::google::protobuf;
44
45 // A buffer big enough to parse descriptor.proto without going to heap.
46 // We use 64-bit ints here to force alignment.
47 int64_t buf[8191];
48
CollectFileDescriptors(const _upb_DefPool_Init * file,std::vector<upb_StringView> & serialized_files,absl::flat_hash_set<const _upb_DefPool_Init * > & seen)49 void CollectFileDescriptors(
50 const _upb_DefPool_Init* file,
51 std::vector<upb_StringView>& serialized_files,
52 absl::flat_hash_set<const _upb_DefPool_Init*>& seen) {
53 if (!seen.insert(file).second) return;
54 for (_upb_DefPool_Init** deps = file->deps; *deps; deps++) {
55 CollectFileDescriptors(*deps, serialized_files, seen);
56 }
57 serialized_files.push_back(file->descriptor);
58 }
59
BM_ArenaOneAlloc(benchmark::State & state)60 static void BM_ArenaOneAlloc(benchmark::State& state) {
61 for (auto _ : state) {
62 upb_Arena* arena = upb_Arena_New();
63 upb_Arena_Malloc(arena, 1);
64 upb_Arena_Free(arena);
65 }
66 }
67 BENCHMARK(BM_ArenaOneAlloc);
68
BM_ArenaInitialBlockOneAlloc(benchmark::State & state)69 static void BM_ArenaInitialBlockOneAlloc(benchmark::State& state) {
70 for (auto _ : state) {
71 upb_Arena* arena = upb_Arena_Init(buf, sizeof(buf), NULL);
72 upb_Arena_Malloc(arena, 1);
73 upb_Arena_Free(arena);
74 }
75 }
76 BENCHMARK(BM_ArenaInitialBlockOneAlloc);
77
BM_ArenaFuseUnbalanced(benchmark::State & state)78 static void BM_ArenaFuseUnbalanced(benchmark::State& state) {
79 std::vector<upb_Arena*> arenas(state.range(0));
80 size_t n = 0;
81 for (auto _ : state) {
82 for (auto& arena : arenas) {
83 arena = upb_Arena_New();
84 }
85 for (auto& arena : arenas) {
86 upb_Arena_Fuse(arenas[0], arena);
87 }
88 for (auto& arena : arenas) {
89 upb_Arena_Free(arena);
90 }
91 n += arenas.size();
92 }
93 state.SetItemsProcessed(n);
94 }
95 BENCHMARK(BM_ArenaFuseUnbalanced)->Range(2, 128);
96
BM_ArenaFuseBalanced(benchmark::State & state)97 static void BM_ArenaFuseBalanced(benchmark::State& state) {
98 std::vector<upb_Arena*> arenas(state.range(0));
99 size_t n = 0;
100
101 for (auto _ : state) {
102 for (auto& arena : arenas) {
103 arena = upb_Arena_New();
104 }
105
106 // Perform a series of fuses that keeps the halves balanced.
107 size_t max = upb_Log2Ceiling(arenas.size());
108 for (size_t n = 0; n <= max; n++) {
109 size_t step = 1 << n;
110 for (size_t i = 0; i + step < arenas.size(); i += (step * 2)) {
111 upb_Arena_Fuse(arenas[i], arenas[i + step]);
112 }
113 }
114
115 for (auto& arena : arenas) {
116 upb_Arena_Free(arena);
117 }
118 n += arenas.size();
119 }
120 state.SetItemsProcessed(n);
121 }
122 BENCHMARK(BM_ArenaFuseBalanced)->Range(2, 128);
123
124 enum LoadDescriptorMode {
125 NoLayout,
126 WithLayout,
127 };
128
129 // This function is mostly copied from upb/def.c, but it is modified to avoid
130 // passing in the pre-generated mini-tables, in order to force upb to compute
131 // them dynamically. Generally you would never want to do this, but we want to
132 // simulate the cost we would pay if we were loading these types purely from
133 // descriptors, with no mini-tales available.
LoadDefInit_BuildLayout(upb_DefPool * s,const _upb_DefPool_Init * init,size_t * bytes)134 bool LoadDefInit_BuildLayout(upb_DefPool* s, const _upb_DefPool_Init* init,
135 size_t* bytes) {
136 _upb_DefPool_Init** deps = init->deps;
137 google_protobuf_FileDescriptorProto* file;
138 upb_Arena* arena;
139 upb_Status status;
140
141 upb_Status_Clear(&status);
142
143 if (upb_DefPool_FindFileByName(s, init->filename)) {
144 return true;
145 }
146
147 arena = upb_Arena_New();
148
149 for (; *deps; deps++) {
150 if (!LoadDefInit_BuildLayout(s, *deps, bytes)) goto err;
151 }
152
153 file = google_protobuf_FileDescriptorProto_parse_ex(
154 init->descriptor.data, init->descriptor.size, NULL,
155 kUpb_DecodeOption_AliasString, arena);
156 *bytes += init->descriptor.size;
157
158 if (!file) {
159 upb_Status_SetErrorFormat(
160 &status,
161 "Failed to parse compiled-in descriptor for file '%s'. This should "
162 "never happen.",
163 init->filename);
164 goto err;
165 }
166
167 // KEY DIFFERENCE: Here we pass in only the descriptor, and not the
168 // pre-generated minitables.
169 if (!upb_DefPool_AddFile(s, file, &status)) {
170 goto err;
171 }
172
173 upb_Arena_Free(arena);
174 return true;
175
176 err:
177 fprintf(stderr,
178 "Error loading compiled-in descriptor for file '%s' (this should "
179 "never happen): %s\n",
180 init->filename, upb_Status_ErrorMessage(&status));
181 exit(1);
182 }
183
184 template <LoadDescriptorMode Mode>
BM_LoadAdsDescriptor_Upb(benchmark::State & state)185 static void BM_LoadAdsDescriptor_Upb(benchmark::State& state) {
186 size_t bytes_per_iter = 0;
187 for (auto _ : state) {
188 upb::DefPool defpool;
189 if (Mode == NoLayout) {
190 google_ads_googleads_v13_services_SearchGoogleAdsRequest_getmsgdef(
191 defpool.ptr());
192 bytes_per_iter = _upb_DefPool_BytesLoaded(defpool.ptr());
193 } else {
194 bytes_per_iter = 0;
195 LoadDefInit_BuildLayout(
196 defpool.ptr(),
197 &google_ads_googleads_v13_services_google_ads_service_proto_upbdefinit,
198 &bytes_per_iter);
199 }
200 }
201 state.SetBytesProcessed(state.iterations() * bytes_per_iter);
202 }
203 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Upb, NoLayout);
204 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Upb, WithLayout);
205
206 template <LoadDescriptorMode Mode>
BM_LoadAdsDescriptor_Proto2(benchmark::State & state)207 static void BM_LoadAdsDescriptor_Proto2(benchmark::State& state) {
208 extern _upb_DefPool_Init
209 google_ads_googleads_v13_services_google_ads_service_proto_upbdefinit;
210 std::vector<upb_StringView> serialized_files;
211 absl::flat_hash_set<const _upb_DefPool_Init*> seen_files;
212 CollectFileDescriptors(
213 &google_ads_googleads_v13_services_google_ads_service_proto_upbdefinit,
214 serialized_files, seen_files);
215 size_t bytes_per_iter = 0;
216 for (auto _ : state) {
217 bytes_per_iter = 0;
218 protobuf::Arena arena;
219 protobuf::DescriptorPool pool;
220 for (auto file : serialized_files) {
221 absl::string_view input(file.data, file.size);
222 auto proto =
223 protobuf::Arena::CreateMessage<protobuf::FileDescriptorProto>(&arena);
224 bool ok = proto->ParseFrom<protobuf::MessageLite::kMergePartial>(input) &&
225 pool.BuildFile(*proto) != nullptr;
226 if (!ok) {
227 printf("Failed to add file.\n");
228 exit(1);
229 }
230 bytes_per_iter += input.size();
231 }
232
233 if (Mode == WithLayout) {
234 protobuf::DynamicMessageFactory factory;
235 const protobuf::Descriptor* d = pool.FindMessageTypeByName(
236 "google.ads.googleads.v13.services.SearchGoogleAdsResponse");
237 if (!d) {
238 printf("Failed to find descriptor.\n");
239 exit(1);
240 }
241 factory.GetPrototype(d);
242 }
243 }
244 state.SetBytesProcessed(state.iterations() * bytes_per_iter);
245 }
246 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Proto2, NoLayout);
247 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Proto2, WithLayout);
248
249 enum CopyStrings {
250 Copy,
251 Alias,
252 };
253
254 enum ArenaMode {
255 NoArena,
256 UseArena,
257 InitBlock,
258 };
259
260 template <ArenaMode AMode, CopyStrings Copy>
BM_Parse_Upb_FileDesc(benchmark::State & state)261 static void BM_Parse_Upb_FileDesc(benchmark::State& state) {
262 for (auto _ : state) {
263 upb_Arena* arena;
264 if (AMode == InitBlock) {
265 arena = upb_Arena_Init(buf, sizeof(buf), NULL);
266 } else {
267 arena = upb_Arena_New();
268 }
269 upb_benchmark_FileDescriptorProto* set =
270 upb_benchmark_FileDescriptorProto_parse_ex(
271 descriptor.data, descriptor.size, NULL,
272 Copy == Alias ? kUpb_DecodeOption_AliasString : 0, arena);
273 if (!set) {
274 printf("Failed to parse.\n");
275 exit(1);
276 }
277 upb_Arena_Free(arena);
278 }
279 state.SetBytesProcessed(state.iterations() * descriptor.size);
280 }
281 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, UseArena, Copy);
282 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, UseArena, Alias);
283 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, InitBlock, Copy);
284 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, InitBlock, Alias);
285
286 template <ArenaMode AMode, class P>
287 struct Proto2Factory;
288
289 template <class P>
290 struct Proto2Factory<NoArena, P> {
291 public:
GetProtoProto2Factory292 P* GetProto() { return &proto_; }
293
294 private:
295 P proto_;
296 };
297
298 template <class P>
299 struct Proto2Factory<UseArena, P> {
300 public:
GetProtoProto2Factory301 P* GetProto() { return protobuf::Arena::CreateMessage<P>(&arena_); }
302
303 private:
304 protobuf::Arena arena_;
305 };
306
307 template <class P>
308 struct Proto2Factory<InitBlock, P> {
309 public:
Proto2FactoryProto2Factory310 Proto2Factory() : arena_(GetOptions()) {}
GetProtoProto2Factory311 P* GetProto() { return protobuf::Arena::CreateMessage<P>(&arena_); }
312
313 private:
GetOptionsProto2Factory314 protobuf::ArenaOptions GetOptions() {
315 protobuf::ArenaOptions opts;
316 opts.initial_block = (char*)buf;
317 opts.initial_block_size = sizeof(buf);
318 return opts;
319 }
320
321 protobuf::Arena arena_;
322 };
323
324 using FileDesc = ::upb_benchmark::FileDescriptorProto;
325 using FileDescSV = ::upb_benchmark::sv::FileDescriptorProto;
326
327 template <class P, ArenaMode AMode, CopyStrings kCopy>
BM_Parse_Proto2(benchmark::State & state)328 void BM_Parse_Proto2(benchmark::State& state) {
329 constexpr protobuf::MessageLite::ParseFlags kParseFlags =
330 kCopy == Copy
331 ? protobuf::MessageLite::ParseFlags::kMergePartial
332 : protobuf::MessageLite::ParseFlags::kMergePartialWithAliasing;
333 for (auto _ : state) {
334 Proto2Factory<AMode, P> proto_factory;
335 auto proto = proto_factory.GetProto();
336 absl::string_view input(descriptor.data, descriptor.size);
337 bool ok = proto->template ParseFrom<kParseFlags>(input);
338 if (!ok) {
339 printf("Failed to parse.\n");
340 exit(1);
341 }
342 }
343 state.SetBytesProcessed(state.iterations() * descriptor.size);
344 }
345 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDesc, NoArena, Copy);
346 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDesc, UseArena, Copy);
347 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDesc, InitBlock, Copy);
348 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDescSV, InitBlock, Alias);
349
BM_SerializeDescriptor_Proto2(benchmark::State & state)350 static void BM_SerializeDescriptor_Proto2(benchmark::State& state) {
351 upb_benchmark::FileDescriptorProto proto;
352 proto.ParseFromArray(descriptor.data, descriptor.size);
353 for (auto _ : state) {
354 proto.SerializePartialToArray(buf, sizeof(buf));
355 }
356 state.SetBytesProcessed(state.iterations() * descriptor.size);
357 }
358 BENCHMARK(BM_SerializeDescriptor_Proto2);
359
BM_SerializeDescriptor_Upb(benchmark::State & state)360 static void BM_SerializeDescriptor_Upb(benchmark::State& state) {
361 int64_t total = 0;
362 upb_Arena* arena = upb_Arena_New();
363 upb_benchmark_FileDescriptorProto* set =
364 upb_benchmark_FileDescriptorProto_parse(descriptor.data, descriptor.size,
365 arena);
366 if (!set) {
367 printf("Failed to parse.\n");
368 exit(1);
369 }
370 for (auto _ : state) {
371 upb_Arena* enc_arena = upb_Arena_Init(buf, sizeof(buf), NULL);
372 size_t size;
373 char* data =
374 upb_benchmark_FileDescriptorProto_serialize(set, enc_arena, &size);
375 if (!data) {
376 printf("Failed to serialize.\n");
377 exit(1);
378 }
379 total += size;
380 }
381 state.SetBytesProcessed(total);
382 }
383 BENCHMARK(BM_SerializeDescriptor_Upb);
384