blob: b7d94b3471cffb1afab6f198399e59dcbe1ad188 [file] [log] [blame]
Alex Vakulenkoe4eec202017-01-27 14:41:04 -08001#include <errno.h>
2#include <fcntl.h>
3#include <time.h>
4#include <unistd.h>
5
6#include <chrono>
7#include <iomanip>
8#include <iostream>
9#include <vector>
10
11#include <pdx/rpc/argument_encoder.h>
12#include <pdx/rpc/message_buffer.h>
13#include <pdx/rpc/payload.h>
14#include <pdx/utility.h>
15
16using namespace android::pdx::rpc;
17using namespace android::pdx;
18using std::placeholders::_1;
19using std::placeholders::_2;
20using std::placeholders::_3;
21using std::placeholders::_4;
22using std::placeholders::_5;
23using std::placeholders::_6;
24
25namespace {
26
27constexpr size_t kMaxStaticBufferSize = 20480;
28
29// Provide numpunct facet that formats numbers with ',' as thousands separators.
30class CommaNumPunct : public std::numpunct<char> {
31 protected:
32 char do_thousands_sep() const override { return ','; }
33 std::string do_grouping() const override { return "\03"; }
34};
35
36class TestPayload : public MessagePayload<SendBuffer>,
37 public MessageWriter,
38 public MessageReader,
39 public NoOpResourceMapper {
40 public:
41 // MessageWriter
42 void* GetNextWriteBufferSection(size_t size) override {
43 const size_t section_offset = Size();
44 Extend(size);
45 return Data() + section_offset;
46 }
47
48 OutputResourceMapper* GetOutputResourceMapper() override { return this; }
49
50 // MessageReader
51 BufferSection GetNextReadBufferSection() override {
52 return {&*ConstCursor(), &*ConstEnd()};
53 }
54
55 void ConsumeReadBufferSectionData(const void* new_start) override {
56 std::advance(ConstCursor(), PointerDistance(new_start, &*ConstCursor()));
57 }
58
59 InputResourceMapper* GetInputResourceMapper() override { return this; }
60};
61
62class StaticBuffer : public MessageWriter,
63 public MessageReader,
64 public NoOpResourceMapper {
65 public:
66 void Clear() {
67 read_ptr_ = buffer_;
68 write_ptr_ = 0;
69 }
70 void Rewind() { read_ptr_ = buffer_; }
71
72 // MessageWriter
73 void* GetNextWriteBufferSection(size_t size) override {
74 void* ptr = buffer_ + write_ptr_;
75 write_ptr_ += size;
76 return ptr;
77 }
78
79 OutputResourceMapper* GetOutputResourceMapper() override { return this; }
80
81 // MessageReader
82 BufferSection GetNextReadBufferSection() override {
83 return {read_ptr_, std::end(buffer_)};
84 }
85
86 void ConsumeReadBufferSectionData(const void* new_start) override {
87 read_ptr_ = static_cast<const uint8_t*>(new_start);
88 }
89
90 InputResourceMapper* GetInputResourceMapper() override { return this; }
91
92 private:
93 uint8_t buffer_[kMaxStaticBufferSize];
94 const uint8_t* read_ptr_{buffer_};
95 size_t write_ptr_{0};
96};
97
98// Simple callback function to clear/reset the input/output buffers for
99// serialization. Using raw function pointer here instead of std::function to
100// minimize the overhead of invocation in the tight test loop over millions of
101// iterations.
102using ResetFunc = void(void*);
103
104// Serialization test function signature, used by the TestRunner.
105using SerializeTestSignature = std::chrono::nanoseconds(MessageWriter* writer,
106 size_t iterations,
107 ResetFunc* write_reset,
108 void* reset_data);
109
110// Deserialization test function signature, used by the TestRunner.
111using DeserializeTestSignature = std::chrono::nanoseconds(
112 MessageReader* reader, MessageWriter* writer, size_t iterations,
113 ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data);
114
115// Generic serialization test runner method. Takes the |value| of type T and
116// serializes it into the output buffer represented by |writer|.
117template <typename T>
118std::chrono::nanoseconds SerializeTestRunner(MessageWriter* writer,
119 size_t iterations,
120 ResetFunc* write_reset,
121 void* reset_data, const T& value) {
122 auto start = std::chrono::high_resolution_clock::now();
123 for (size_t i = 0; i < iterations; i++) {
124 write_reset(reset_data);
125 Serialize(value, writer);
126 }
127 auto stop = std::chrono::high_resolution_clock::now();
128 return stop - start;
129}
130
131// Generic deserialization test runner method. Takes the |value| of type T and
132// temporarily serializes it into the output buffer, then repeatedly
133// deserializes the data back from that buffer.
134template <typename T>
135std::chrono::nanoseconds DeserializeTestRunner(
136 MessageReader* reader, MessageWriter* writer, size_t iterations,
137 ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data,
138 const T& value) {
139 write_reset(reset_data);
140 Serialize(value, writer);
141 T output_data;
142 auto start = std::chrono::high_resolution_clock::now();
143 for (size_t i = 0; i < iterations; i++) {
144 read_reset(reset_data);
145 Deserialize(&output_data, reader);
146 }
147 auto stop = std::chrono::high_resolution_clock::now();
148 if (output_data != value)
149 return start - stop; // Return negative value to indicate error.
150 return stop - start;
151}
152
153// Special version of SerializeTestRunner that doesn't perform any serialization
154// but does all the same setup steps and moves data of size |data_size| into
155// the output buffer. Useful to determine the baseline to calculate time used
156// just for serialization layer.
157std::chrono::nanoseconds SerializeBaseTest(MessageWriter* writer,
158 size_t iterations,
159 ResetFunc* write_reset,
160 void* reset_data, size_t data_size) {
161 std::vector<uint8_t> dummy_data(data_size);
162 auto start = std::chrono::high_resolution_clock::now();
163 for (size_t i = 0; i < iterations; i++) {
164 write_reset(reset_data);
165 memcpy(writer->GetNextWriteBufferSection(dummy_data.size()),
166 dummy_data.data(), dummy_data.size());
167 }
168 auto stop = std::chrono::high_resolution_clock::now();
169 return stop - start;
170}
171
172// Special version of DeserializeTestRunner that doesn't perform any
173// deserialization but invokes Rewind on the input buffer repeatedly.
174// Useful to determine the baseline to calculate time used just for
175// deserialization layer.
176std::chrono::nanoseconds DeserializeBaseTest(
177 MessageReader* reader, MessageWriter* writer, size_t iterations,
178 ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data,
179 size_t data_size) {
180 std::vector<uint8_t> dummy_data(data_size);
181 write_reset(reset_data);
182 memcpy(writer->GetNextWriteBufferSection(dummy_data.size()),
183 dummy_data.data(), dummy_data.size());
184 auto start = std::chrono::high_resolution_clock::now();
185 for (size_t i = 0; i < iterations; i++) {
186 read_reset(reset_data);
187 auto section = reader->GetNextReadBufferSection();
188 memcpy(dummy_data.data(), section.first, dummy_data.size());
189 reader->ConsumeReadBufferSectionData(
190 AdvancePointer(section.first, dummy_data.size()));
191 }
192 auto stop = std::chrono::high_resolution_clock::now();
193 return stop - start;
194}
195
196// The main class that accumulates individual tests to be executed.
197class TestRunner {
198 public:
199 struct BufferInfo {
200 BufferInfo(const std::string& buffer_name, MessageReader* reader,
201 MessageWriter* writer, ResetFunc* read_reset_func,
202 ResetFunc* write_reset_func, void* reset_data)
203 : name{buffer_name},
204 reader{reader},
205 writer{writer},
206 read_reset_func{read_reset_func},
207 write_reset_func{write_reset_func},
208 reset_data{reset_data} {}
209 std::string name;
210 MessageReader* reader;
211 MessageWriter* writer;
212 ResetFunc* read_reset_func;
213 ResetFunc* write_reset_func;
214 void* reset_data;
215 };
216
217 void AddTestFunc(const std::string& name,
218 std::function<SerializeTestSignature> serialize_test,
219 std::function<DeserializeTestSignature> deserialize_test,
220 size_t data_size) {
221 tests_.emplace_back(name, std::move(serialize_test),
222 std::move(deserialize_test), data_size);
223 }
224
225 template <typename T>
226 void AddSerializationTest(const std::string& name, T&& value) {
227 const size_t data_size = GetSerializedSize(value);
228 auto serialize_test =
229 std::bind(static_cast<std::chrono::nanoseconds (*)(
230 MessageWriter*, size_t, ResetFunc*, void*, const T&)>(
231 &SerializeTestRunner),
232 _1, _2, _3, _4, std::forward<T>(value));
233 tests_.emplace_back(name, std::move(serialize_test),
234 std::function<DeserializeTestSignature>{}, data_size);
235 }
236
237 template <typename T>
238 void AddDeserializationTest(const std::string& name, T&& value) {
239 const size_t data_size = GetSerializedSize(value);
240 auto deserialize_test =
241 std::bind(static_cast<std::chrono::nanoseconds (*)(
242 MessageReader*, MessageWriter*, size_t, ResetFunc*,
243 ResetFunc*, void*, const T&)>(&DeserializeTestRunner),
244 _1, _2, _3, _4, _5, _6, std::forward<T>(value));
245 tests_.emplace_back(name, std::function<SerializeTestSignature>{},
246 std::move(deserialize_test), data_size);
247 }
248
249 template <typename T>
250 void AddTest(const std::string& name, T&& value) {
251 const size_t data_size = GetSerializedSize(value);
252 if (data_size > kMaxStaticBufferSize) {
253 std::cerr << "Test '" << name << "' requires " << data_size
254 << " bytes in the serialization buffer but only "
255 << kMaxStaticBufferSize << " are available." << std::endl;
256 exit(1);
257 }
258 auto serialize_test =
259 std::bind(static_cast<std::chrono::nanoseconds (*)(
260 MessageWriter*, size_t, ResetFunc*, void*, const T&)>(
261 &SerializeTestRunner),
262 _1, _2, _3, _4, value);
263 auto deserialize_test =
264 std::bind(static_cast<std::chrono::nanoseconds (*)(
265 MessageReader*, MessageWriter*, size_t, ResetFunc*,
266 ResetFunc*, void*, const T&)>(&DeserializeTestRunner),
267 _1, _2, _3, _4, _5, _6, std::forward<T>(value));
268 tests_.emplace_back(name, std::move(serialize_test),
269 std::move(deserialize_test), data_size);
270 }
271
272 std::string CenterString(std::string text, size_t column_width) {
273 if (text.size() < column_width) {
274 text = std::string((column_width - text.size()) / 2, ' ') + text;
275 }
276 return text;
277 }
278
279 void RunTests(size_t iteration_count,
280 const std::vector<BufferInfo>& buffers) {
281 using float_seconds = std::chrono::duration<double>;
282 const std::string name_column_separator = " : ";
283 const std::string buffer_column_separator = " || ";
284 const std::string buffer_timing_column_separator = " | ";
285 const size_t data_size_column_width = 6;
286 const size_t time_column_width = 9;
287 const size_t qps_column_width = 18;
288 const size_t buffer_column_width = time_column_width +
289 buffer_timing_column_separator.size() +
290 qps_column_width;
291
292 auto compare_name_length = [](const TestEntry& t1, const TestEntry& t2) {
293 return t1.name.size() < t2.name.size();
294 };
295 auto test_with_longest_name =
296 std::max_element(tests_.begin(), tests_.end(), compare_name_length);
297 size_t name_column_width = test_with_longest_name->name.size();
298
299 size_t total_width =
300 name_column_width + name_column_separator.size() +
301 data_size_column_width + buffer_column_separator.size() +
302 buffers.size() * (buffer_column_width + buffer_column_separator.size());
303
304 const std::string dbl_separator(total_width, '=');
305 const std::string separator(total_width, '-');
306
307 auto print_header = [&](const std::string& header) {
308 std::cout << dbl_separator << std::endl;
309 std::stringstream ss;
310 ss.imbue(std::locale(ss.getloc(), new CommaNumPunct));
311 ss << header << " (" << iteration_count << " iterations)";
312 std::cout << CenterString(ss.str(), total_width) << std::endl;
313 std::cout << dbl_separator << std::endl;
314 std::cout << std::setw(name_column_width) << "Test Name" << std::left
315 << name_column_separator << std::setw(data_size_column_width)
316 << CenterString("Size", data_size_column_width)
317 << buffer_column_separator;
318 for (const auto& buffer_info : buffers) {
319 std::cout << std::setw(buffer_column_width)
320 << CenterString(buffer_info.name, buffer_column_width)
321 << buffer_column_separator;
322 }
323 std::cout << std::endl;
324 std::cout << std::setw(name_column_width) << "" << name_column_separator
325 << std::setw(data_size_column_width)
326 << CenterString("bytes", data_size_column_width)
327 << buffer_column_separator << std::left;
328 for (size_t i = 0; i < buffers.size(); i++) {
329 std::cout << std::setw(time_column_width)
330 << CenterString("Time, s", time_column_width)
331 << buffer_timing_column_separator
332 << std::setw(qps_column_width)
333 << CenterString("QPS", qps_column_width)
334 << buffer_column_separator;
335 }
336 std::cout << std::right << std::endl;
337 std::cout << separator << std::endl;
338 };
339
340 print_header("Serialization benchmarks");
341 for (const auto& test : tests_) {
342 if (test.serialize_test) {
343 std::cout << std::setw(name_column_width) << test.name << " : "
344 << std::setw(data_size_column_width) << test.data_size
345 << buffer_column_separator;
346 for (const auto& buffer_info : buffers) {
347 auto seconds =
348 std::chrono::duration_cast<float_seconds>(test.serialize_test(
349 buffer_info.writer, iteration_count,
350 buffer_info.write_reset_func, buffer_info.reset_data));
351 double qps = iteration_count / seconds.count();
352 std::cout << std::fixed << std::setprecision(3)
353 << std::setw(time_column_width) << seconds.count()
354 << buffer_timing_column_separator
355 << std::setw(qps_column_width) << qps
356 << buffer_column_separator;
357 }
358 std::cout << std::endl;
359 }
360 }
361
362 print_header("Deserialization benchmarks");
363 for (const auto& test : tests_) {
364 if (test.deserialize_test) {
365 std::cout << std::setw(name_column_width) << test.name << " : "
366 << std::setw(data_size_column_width) << test.data_size
367 << buffer_column_separator;
368 for (const auto& buffer_info : buffers) {
369 auto seconds =
370 std::chrono::duration_cast<float_seconds>(test.deserialize_test(
371 buffer_info.reader, buffer_info.writer, iteration_count,
372 buffer_info.read_reset_func, buffer_info.write_reset_func,
373 buffer_info.reset_data));
374 double qps = iteration_count / seconds.count();
375 std::cout << std::fixed << std::setprecision(3)
376 << std::setw(time_column_width) << seconds.count()
377 << buffer_timing_column_separator
378 << std::setw(qps_column_width) << qps
379 << buffer_column_separator;
380 }
381 std::cout << std::endl;
382 }
383 }
384 std::cout << dbl_separator << std::endl;
385 }
386
387 private:
388 struct TestEntry {
389 TestEntry(const std::string& test_name,
390 std::function<SerializeTestSignature> serialize_test,
391 std::function<DeserializeTestSignature> deserialize_test,
392 size_t data_size)
393 : name{test_name},
394 serialize_test{std::move(serialize_test)},
395 deserialize_test{std::move(deserialize_test)},
396 data_size{data_size} {}
397 std::string name;
398 std::function<SerializeTestSignature> serialize_test;
399 std::function<DeserializeTestSignature> deserialize_test;
400 size_t data_size;
401 };
402
403 std::vector<TestEntry> tests_;
404};
405
406std::string GenerateContainerName(const std::string& type, size_t count) {
407 std::stringstream ss;
408 ss << type << "(" << count << ")";
409 return ss.str();
410}
411
412} // anonymous namespace
413
414int main(int /*argc*/, char** /*argv*/) {
415 const size_t iteration_count = 10000000; // 10M iterations.
416 TestRunner test_runner;
417 std::cout.imbue(std::locale(std::cout.getloc(), new CommaNumPunct));
418
419 // Baseline tests to figure out the overhead of buffer resizing and data
420 // transfers.
421 for (size_t len : {0, 1, 9, 66, 259}) {
422 auto serialize_base_test =
423 std::bind(&SerializeBaseTest, _1, _2, _3, _4, len);
424 auto deserialize_base_test =
425 std::bind(&DeserializeBaseTest, _1, _2, _3, _4, _5, _6, len);
426 test_runner.AddTestFunc("--Baseline--", std::move(serialize_base_test),
427 std::move(deserialize_base_test), len);
428 }
429
430 // Individual serialization/deserialization tests.
431 test_runner.AddTest("bool", true);
432 test_runner.AddTest("int32_t", 12);
433
434 for (size_t len : {0, 1, 8, 64, 256}) {
435 test_runner.AddTest(GenerateContainerName("string", len),
436 std::string(len, '*'));
437 }
438 // Serialization is too slow to handle such large strings, add this test for
439 // deserialization only.
440 test_runner.AddDeserializationTest(GenerateContainerName("string", 10240),
441 std::string(10240, '*'));
442
443 for (size_t len : {0, 1, 8, 64, 256}) {
444 std::vector<int32_t> int_vector(len);
445 std::iota(int_vector.begin(), int_vector.end(), 0);
446 test_runner.AddTest(GenerateContainerName("vector<int32_t>", len),
447 std::move(int_vector));
448 }
449
450 std::vector<std::string> vector_of_strings = {
451 "012345678901234567890123456789", "012345678901234567890123456789",
452 "012345678901234567890123456789", "012345678901234567890123456789",
453 "012345678901234567890123456789",
454 };
455 test_runner.AddTest(
456 GenerateContainerName("vector<string>", vector_of_strings.size()),
457 std::move(vector_of_strings));
458
459 test_runner.AddTest("tuple<int, bool, string, double>",
460 std::make_tuple(123, true, std::string{"foobar"}, 1.1));
461
462 for (size_t len : {0, 1, 8, 64}) {
463 std::map<int, std::string> test_map;
464 for (size_t i = 0; i < len; i++)
465 test_map.emplace(i, std::to_string(i));
466 test_runner.AddTest(GenerateContainerName("map<int, string>", len),
467 std::move(test_map));
468 }
469
470 for (size_t len : {0, 1, 8, 64}) {
471 std::unordered_map<int, std::string> test_map;
472 for (size_t i = 0; i < len; i++)
473 test_map.emplace(i, std::to_string(i));
474 test_runner.AddTest(
475 GenerateContainerName("unordered_map<int, string>", len),
476 std::move(test_map));
477 }
478
479 // BufferWrapper can't be used with deserialization tests right now because
480 // it requires external buffer to be filled in, which is not available.
481 std::vector<std::vector<uint8_t>> data_buffers;
482 for (size_t len : {0, 1, 8, 64, 256}) {
483 data_buffers.emplace_back(len);
484 test_runner.AddSerializationTest(
485 GenerateContainerName("BufferWrapper<uint8_t*>", len),
486 BufferWrapper<uint8_t*>(data_buffers.back().data(),
487 data_buffers.back().size()));
488 }
489
490 // Various backing buffers to run the tests on.
491 std::vector<TestRunner::BufferInfo> buffers;
492
493 Payload buffer;
494 buffers.emplace_back("Non-TLS Buffer", &buffer, &buffer,
495 [](void* ptr) { static_cast<Payload*>(ptr)->Rewind(); },
496 [](void* ptr) { static_cast<Payload*>(ptr)->Clear(); },
497 &buffer);
498
499 TestPayload tls_buffer;
500 buffers.emplace_back(
501 "TLS Buffer", &tls_buffer, &tls_buffer,
502 [](void* ptr) { static_cast<TestPayload*>(ptr)->Rewind(); },
503 [](void* ptr) { static_cast<TestPayload*>(ptr)->Clear(); }, &tls_buffer);
504
505 StaticBuffer static_buffer;
506 buffers.emplace_back(
507 "Static Buffer", &static_buffer, &static_buffer,
508 [](void* ptr) { static_cast<StaticBuffer*>(ptr)->Rewind(); },
509 [](void* ptr) { static_cast<StaticBuffer*>(ptr)->Clear(); },
510 &static_buffer);
511
512 // Finally, run all the tests.
513 test_runner.RunTests(iteration_count, buffers);
514 return 0;
515}