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