• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google LLC.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include <benchmark/benchmark.h>
9 
10 #include <math.h>
11 #include <stdint.h>
12 #include <string.h>
13 
14 #include <string>
15 #include <vector>
16 
17 #include "google/ads/googleads/v16/services/google_ads_service.upbdefs.h"
18 #include "google/protobuf/descriptor.pb.h"
19 #include "absl/container/flat_hash_set.h"
20 #include "absl/log/absl_check.h"
21 #include "google/protobuf/dynamic_message.h"
22 #include "google/protobuf/json/json.h"
23 #include "benchmarks/descriptor.pb.h"
24 #include "benchmarks/descriptor.upb.h"
25 #include "benchmarks/descriptor.upbdefs.h"
26 #include "benchmarks/descriptor_sv.pb.h"
27 #include "upb/base/string_view.h"
28 #include "upb/base/upcast.h"
29 #include "upb/json/decode.h"
30 #include "upb/json/encode.h"
31 #include "upb/mem/arena.h"
32 #include "upb/reflection/def.hpp"
33 #include "upb/wire/decode.h"
34 
35 upb_StringView descriptor =
36     benchmarks_descriptor_proto_upbdefinit.descriptor;
37 namespace protobuf = ::google::protobuf;
38 
39 // A buffer big enough to parse descriptor.proto without going to heap.
40 // We use 64-bit ints here to force alignment.
41 int64_t buf[8191];
42 
CollectFileDescriptors(const _upb_DefPool_Init * file,std::vector<upb_StringView> & serialized_files,absl::flat_hash_set<const _upb_DefPool_Init * > & seen)43 void CollectFileDescriptors(
44     const _upb_DefPool_Init* file,
45     std::vector<upb_StringView>& serialized_files,
46     absl::flat_hash_set<const _upb_DefPool_Init*>& seen) {
47   if (!seen.insert(file).second) return;
48   for (_upb_DefPool_Init** deps = file->deps; *deps; deps++) {
49     CollectFileDescriptors(*deps, serialized_files, seen);
50   }
51   serialized_files.push_back(file->descriptor);
52 }
53 
BM_ArenaOneAlloc(benchmark::State & state)54 static void BM_ArenaOneAlloc(benchmark::State& state) {
55   for (auto _ : state) {
56     upb_Arena* arena = upb_Arena_New();
57     upb_Arena_Malloc(arena, 1);
58     upb_Arena_Free(arena);
59   }
60 }
61 BENCHMARK(BM_ArenaOneAlloc);
62 
BM_ArenaInitialBlockOneAlloc(benchmark::State & state)63 static void BM_ArenaInitialBlockOneAlloc(benchmark::State& state) {
64   for (auto _ : state) {
65     upb_Arena* arena = upb_Arena_Init(buf, sizeof(buf), nullptr);
66     upb_Arena_Malloc(arena, 1);
67     upb_Arena_Free(arena);
68   }
69 }
70 BENCHMARK(BM_ArenaInitialBlockOneAlloc);
71 
BM_ArenaFuseUnbalanced(benchmark::State & state)72 static void BM_ArenaFuseUnbalanced(benchmark::State& state) {
73   std::vector<upb_Arena*> arenas(state.range(0));
74   size_t n = 0;
75   for (auto _ : state) {
76     for (auto& arena : arenas) {
77       arena = upb_Arena_New();
78     }
79     for (auto& arena : arenas) {
80       upb_Arena_Fuse(arenas[0], arena);
81     }
82     for (auto& arena : arenas) {
83       upb_Arena_Free(arena);
84     }
85     n += arenas.size();
86   }
87   state.SetItemsProcessed(n);
88 }
89 BENCHMARK(BM_ArenaFuseUnbalanced)->Range(2, 128);
90 
BM_ArenaFuseBalanced(benchmark::State & state)91 static void BM_ArenaFuseBalanced(benchmark::State& state) {
92   std::vector<upb_Arena*> arenas(state.range(0));
93   size_t n = 0;
94 
95   for (auto _ : state) {
96     for (auto& arena : arenas) {
97       arena = upb_Arena_New();
98     }
99 
100     // Perform a series of fuses that keeps the halves balanced.
101     const size_t max = ceil(log2(double(arenas.size())));
102     for (size_t n = 0; n <= max; n++) {
103       size_t step = 1 << n;
104       for (size_t i = 0; i + step < arenas.size(); i += (step * 2)) {
105         upb_Arena_Fuse(arenas[i], arenas[i + step]);
106       }
107     }
108 
109     for (auto& arena : arenas) {
110       upb_Arena_Free(arena);
111     }
112     n += arenas.size();
113   }
114   state.SetItemsProcessed(n);
115 }
116 BENCHMARK(BM_ArenaFuseBalanced)->Range(2, 128);
117 
118 enum LoadDescriptorMode {
119   NoLayout,
120   WithLayout,
121 };
122 
123 // This function is mostly copied from upb/def.c, but it is modified to avoid
124 // passing in the pre-generated mini-tables, in order to force upb to compute
125 // them dynamically.  Generally you would never want to do this, but we want to
126 // simulate the cost we would pay if we were loading these types purely from
127 // descriptors, with no mini-tales available.
LoadDefInit_BuildLayout(upb_DefPool * s,const _upb_DefPool_Init * init,size_t * bytes)128 bool LoadDefInit_BuildLayout(upb_DefPool* s, const _upb_DefPool_Init* init,
129                              size_t* bytes) {
130   _upb_DefPool_Init** deps = init->deps;
131   google_protobuf_FileDescriptorProto* file;
132   upb_Arena* arena;
133   upb_Status status;
134 
135   upb_Status_Clear(&status);
136 
137   if (upb_DefPool_FindFileByName(s, init->filename)) {
138     return true;
139   }
140 
141   arena = upb_Arena_New();
142 
143   for (; *deps; deps++) {
144     if (!LoadDefInit_BuildLayout(s, *deps, bytes)) goto err;
145   }
146 
147   file = google_protobuf_FileDescriptorProto_parse_ex(
148       init->descriptor.data, init->descriptor.size, nullptr,
149       kUpb_DecodeOption_AliasString, arena);
150   *bytes += init->descriptor.size;
151 
152   if (!file) {
153     upb_Status_SetErrorFormat(
154         &status,
155         "Failed to parse compiled-in descriptor for file '%s'. This should "
156         "never happen.",
157         init->filename);
158     goto err;
159   }
160 
161   // KEY DIFFERENCE: Here we pass in only the descriptor, and not the
162   // pre-generated minitables.
163   if (!upb_DefPool_AddFile(s, file, &status)) {
164     goto err;
165   }
166 
167   upb_Arena_Free(arena);
168   return true;
169 
170 err:
171   fprintf(stderr,
172           "Error loading compiled-in descriptor for file '%s' (this should "
173           "never happen): %s\n",
174           init->filename, upb_Status_ErrorMessage(&status));
175   exit(1);
176 }
177 
178 template <LoadDescriptorMode Mode>
BM_LoadAdsDescriptor_Upb(benchmark::State & state)179 static void BM_LoadAdsDescriptor_Upb(benchmark::State& state) {
180   size_t bytes_per_iter = 0;
181   for (auto _ : state) {
182     upb::DefPool defpool;
183     if (Mode == NoLayout) {
184       google_ads_googleads_v16_services_SearchGoogleAdsRequest_getmsgdef(
185           defpool.ptr());
186       bytes_per_iter = _upb_DefPool_BytesLoaded(defpool.ptr());
187     } else {
188       bytes_per_iter = 0;
189       LoadDefInit_BuildLayout(
190           defpool.ptr(),
191           &google_ads_googleads_v16_services_google_ads_service_proto_upbdefinit,
192           &bytes_per_iter);
193     }
194   }
195   state.SetBytesProcessed(state.iterations() * bytes_per_iter);
196 }
197 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Upb, NoLayout);
198 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Upb, WithLayout);
199 
200 template <LoadDescriptorMode Mode>
BM_LoadAdsDescriptor_Proto2(benchmark::State & state)201 static void BM_LoadAdsDescriptor_Proto2(benchmark::State& state) {
202   extern _upb_DefPool_Init
203       google_ads_googleads_v16_services_google_ads_service_proto_upbdefinit;
204   std::vector<upb_StringView> serialized_files;
205   absl::flat_hash_set<const _upb_DefPool_Init*> seen_files;
206   CollectFileDescriptors(
207       &google_ads_googleads_v16_services_google_ads_service_proto_upbdefinit,
208       serialized_files, seen_files);
209   size_t bytes_per_iter = 0;
210   for (auto _ : state) {
211     bytes_per_iter = 0;
212     protobuf::Arena arena;
213     protobuf::DescriptorPool pool;
214     for (auto file : serialized_files) {
215       absl::string_view input(file.data, file.size);
216       auto proto =
217           protobuf::Arena::Create<protobuf::FileDescriptorProto>(&arena);
218       bool ok = proto->ParseFrom<protobuf::MessageLite::kMergePartial>(input) &&
219                 pool.BuildFile(*proto) != nullptr;
220       if (!ok) {
221         printf("Failed to add file.\n");
222         exit(1);
223       }
224       bytes_per_iter += input.size();
225     }
226 
227     if (Mode == WithLayout) {
228       protobuf::DynamicMessageFactory factory;
229       const protobuf::Descriptor* d = pool.FindMessageTypeByName(
230           "google.ads.googleads.v16.services.SearchGoogleAdsResponse");
231       if (!d) {
232         printf("Failed to find descriptor.\n");
233         exit(1);
234       }
235       factory.GetPrototype(d);
236     }
237   }
238   state.SetBytesProcessed(state.iterations() * bytes_per_iter);
239 }
240 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Proto2, NoLayout);
241 BENCHMARK_TEMPLATE(BM_LoadAdsDescriptor_Proto2, WithLayout);
242 
243 enum CopyStrings {
244   Copy,
245   Alias,
246 };
247 
248 enum ArenaMode {
249   NoArena,
250   UseArena,
251   InitBlock,
252 };
253 
254 template <ArenaMode AMode, CopyStrings Copy>
BM_Parse_Upb_FileDesc(benchmark::State & state)255 static void BM_Parse_Upb_FileDesc(benchmark::State& state) {
256   for (auto _ : state) {
257     upb_Arena* arena;
258     if (AMode == InitBlock) {
259       arena = upb_Arena_Init(buf, sizeof(buf), nullptr);
260     } else {
261       arena = upb_Arena_New();
262     }
263     upb_benchmark_FileDescriptorProto* set =
264         upb_benchmark_FileDescriptorProto_parse_ex(
265             descriptor.data, descriptor.size, nullptr,
266             Copy == Alias ? kUpb_DecodeOption_AliasString : 0, arena);
267     if (!set) {
268       printf("Failed to parse.\n");
269       exit(1);
270     }
271     upb_Arena_Free(arena);
272   }
273   state.SetBytesProcessed(state.iterations() * descriptor.size);
274 }
275 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, UseArena, Copy);
276 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, UseArena, Alias);
277 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, InitBlock, Copy);
278 BENCHMARK_TEMPLATE(BM_Parse_Upb_FileDesc, InitBlock, Alias);
279 
280 template <ArenaMode AMode, class P>
281 struct Proto2Factory;
282 
283 template <class P>
284 struct Proto2Factory<NoArena, P> {
285  public:
GetProtoProto2Factory286   P* GetProto() { return &proto; }
287 
288  private:
289   P proto;
290 };
291 
292 template <class P>
293 struct Proto2Factory<UseArena, P> {
294  public:
GetProtoProto2Factory295   P* GetProto() { return protobuf::Arena::Create<P>(&arena); }
296 
297  private:
298   protobuf::Arena arena;
299 };
300 
301 template <class P>
302 struct Proto2Factory<InitBlock, P> {
303  public:
Proto2FactoryProto2Factory304   Proto2Factory() : arena(GetOptions()) {}
GetProtoProto2Factory305   P* GetProto() { return protobuf::Arena::Create<P>(&arena); }
306 
307  private:
GetOptionsProto2Factory308   protobuf::ArenaOptions GetOptions() {
309     protobuf::ArenaOptions opts;
310     opts.initial_block = (char*)buf;
311     opts.initial_block_size = sizeof(buf);
312     return opts;
313   }
314 
315   protobuf::Arena arena;
316 };
317 
318 using FileDesc = ::upb_benchmark::FileDescriptorProto;
319 using FileDescSV = ::upb_benchmark::sv::FileDescriptorProto;
320 
321 template <class P, ArenaMode AMode, CopyStrings kCopy>
BM_Parse_Proto2(benchmark::State & state)322 void BM_Parse_Proto2(benchmark::State& state) {
323   constexpr protobuf::MessageLite::ParseFlags kParseFlags =
324       kCopy == Copy
325           ? protobuf::MessageLite::ParseFlags::kMergePartial
326           : protobuf::MessageLite::ParseFlags::kMergePartialWithAliasing;
327   for (auto _ : state) {
328     Proto2Factory<AMode, P> proto_factory;
329     auto proto = proto_factory.GetProto();
330     absl::string_view input(descriptor.data, descriptor.size);
331     bool ok = proto->template ParseFrom<kParseFlags>(input);
332     if (!ok) {
333       printf("Failed to parse.\n");
334       exit(1);
335     }
336   }
337   state.SetBytesProcessed(state.iterations() * descriptor.size);
338 }
339 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDesc, NoArena, Copy);
340 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDesc, UseArena, Copy);
341 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDesc, InitBlock, Copy);
342 BENCHMARK_TEMPLATE(BM_Parse_Proto2, FileDescSV, InitBlock, Alias);
343 
BM_SerializeDescriptor_Proto2(benchmark::State & state)344 static void BM_SerializeDescriptor_Proto2(benchmark::State& state) {
345   upb_benchmark::FileDescriptorProto proto;
346   proto.ParseFromArray(descriptor.data, descriptor.size);
347   for (auto _ : state) {
348     proto.SerializePartialToArray(buf, sizeof(buf));
349   }
350   state.SetBytesProcessed(state.iterations() * descriptor.size);
351 }
352 BENCHMARK(BM_SerializeDescriptor_Proto2);
353 
UpbParseDescriptor(upb_Arena * arena)354 static upb_benchmark_FileDescriptorProto* UpbParseDescriptor(upb_Arena* arena) {
355   upb_benchmark_FileDescriptorProto* set =
356       upb_benchmark_FileDescriptorProto_parse(descriptor.data, descriptor.size,
357                                               arena);
358   if (!set) {
359     printf("Failed to parse.\n");
360     exit(1);
361   }
362   return set;
363 }
364 
BM_SerializeDescriptor_Upb(benchmark::State & state)365 static void BM_SerializeDescriptor_Upb(benchmark::State& state) {
366   int64_t total = 0;
367   upb_Arena* arena = upb_Arena_New();
368   upb_benchmark_FileDescriptorProto* set = UpbParseDescriptor(arena);
369   for (auto _ : state) {
370     upb_Arena* enc_arena = upb_Arena_Init(buf, sizeof(buf), nullptr);
371     size_t size;
372     char* data =
373         upb_benchmark_FileDescriptorProto_serialize(set, enc_arena, &size);
374     if (!data) {
375       printf("Failed to serialize.\n");
376       exit(1);
377     }
378     total += size;
379   }
380   state.SetBytesProcessed(total);
381 }
382 BENCHMARK(BM_SerializeDescriptor_Upb);
383 
UpbJsonEncode(upb_benchmark_FileDescriptorProto * proto,const upb_MessageDef * md,upb_Arena * arena)384 static absl::string_view UpbJsonEncode(upb_benchmark_FileDescriptorProto* proto,
385                                        const upb_MessageDef* md,
386                                        upb_Arena* arena) {
387   size_t size =
388       upb_JsonEncode(UPB_UPCAST(proto), md, nullptr, 0, nullptr, 0, nullptr);
389   char* buf = reinterpret_cast<char*>(upb_Arena_Malloc(arena, size + 1));
390   upb_JsonEncode(UPB_UPCAST(proto), md, nullptr, 0, buf, size, nullptr);
391   return absl::string_view(buf, size);
392 }
393 
BM_JsonParse_Upb(benchmark::State & state)394 static void BM_JsonParse_Upb(benchmark::State& state) {
395   upb_Arena* arena = upb_Arena_New();
396   upb_benchmark_FileDescriptorProto* set =
397       upb_benchmark_FileDescriptorProto_parse(descriptor.data, descriptor.size,
398                                               arena);
399   if (!set) {
400     printf("Failed to parse.\n");
401     exit(1);
402   }
403 
404   upb::DefPool defpool;
405   const upb_MessageDef* md =
406       upb_benchmark_FileDescriptorProto_getmsgdef(defpool.ptr());
407   auto json = UpbJsonEncode(set, md, arena);
408 
409   for (auto _ : state) {
410     upb_Arena* arena = upb_Arena_New();
411     upb_benchmark_FileDescriptorProto* proto =
412         upb_benchmark_FileDescriptorProto_new(arena);
413     upb_JsonDecode(json.data(), json.size(), UPB_UPCAST(proto), md,
414                    defpool.ptr(), 0, arena, nullptr);
415     upb_Arena_Free(arena);
416   }
417   state.SetBytesProcessed(state.iterations() * json.size());
418 }
419 BENCHMARK(BM_JsonParse_Upb);
420 
BM_JsonParse_Proto2(benchmark::State & state)421 static void BM_JsonParse_Proto2(benchmark::State& state) {
422   protobuf::FileDescriptorProto proto;
423   absl::string_view input(descriptor.data, descriptor.size);
424   proto.ParseFromString(input);
425   std::string json;
426   ABSL_CHECK_OK(google::protobuf::json::MessageToJsonString(proto, &json));
427   for (auto _ : state) {
428     protobuf::FileDescriptorProto proto;
429     ABSL_CHECK_OK(google::protobuf::json::JsonStringToMessage(json, &proto));
430   }
431   state.SetBytesProcessed(state.iterations() * json.size());
432 }
433 BENCHMARK(BM_JsonParse_Proto2);
434 
BM_JsonSerialize_Upb(benchmark::State & state)435 static void BM_JsonSerialize_Upb(benchmark::State& state) {
436   upb_Arena* arena = upb_Arena_New();
437   upb_benchmark_FileDescriptorProto* set =
438       upb_benchmark_FileDescriptorProto_parse(descriptor.data, descriptor.size,
439                                               arena);
440   ABSL_CHECK(set != nullptr);
441 
442   upb::DefPool defpool;
443   const upb_MessageDef* md =
444       upb_benchmark_FileDescriptorProto_getmsgdef(defpool.ptr());
445   auto json = UpbJsonEncode(set, md, arena);
446   std::string json_str;
447   json_str.resize(json.size());
448 
449   for (auto _ : state) {
450     // This isn't a fully fair comparison, as it assumes we already know the
451     // correct size of the buffer.  In practice, we usually need to run the
452     // encoder twice, once to discover the size of the buffer.
453     upb_JsonEncode(UPB_UPCAST(set), md, nullptr, 0, json_str.data(),
454                    json_str.size(), nullptr);
455   }
456   state.SetBytesProcessed(state.iterations() * json.size());
457 }
458 BENCHMARK(BM_JsonSerialize_Upb);
459 
BM_JsonSerialize_Proto2(benchmark::State & state)460 static void BM_JsonSerialize_Proto2(benchmark::State& state) {
461   protobuf::FileDescriptorProto proto;
462   absl::string_view input(descriptor.data, descriptor.size);
463   proto.ParseFromString(input);
464   std::string json;
465   for (auto _ : state) {
466     json.clear();
467     ABSL_CHECK_OK(google::protobuf::json::MessageToJsonString(proto, &json));
468   }
469   state.SetBytesProcessed(state.iterations() * json.size());
470 }
471 BENCHMARK(BM_JsonSerialize_Proto2);
472