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