Home | History | Annotate | Download | only in libpdx
      1 #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 
     16 using namespace android::pdx::rpc;
     17 using namespace android::pdx;
     18 using std::placeholders::_1;
     19 using std::placeholders::_2;
     20 using std::placeholders::_3;
     21 using std::placeholders::_4;
     22 using std::placeholders::_5;
     23 using std::placeholders::_6;
     24 
     25 namespace {
     26 
     27 constexpr size_t kMaxStaticBufferSize = 20480;
     28 
     29 // Provide numpunct facet that formats numbers with ',' as thousands separators.
     30 class 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 
     36 class 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 
     62 class 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.
    102 using ResetFunc = void(void*);
    103 
    104 // Serialization test function signature, used by the TestRunner.
    105 using 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.
    111 using 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|.
    117 template <typename T>
    118 std::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.
    134 template <typename T>
    135 std::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.
    157 std::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.
    176 std::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.
    197 class 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 
    406 std::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 
    414 int 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 }
    516