123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- #include <errno.h>
- #include <fcntl.h>
- #include <time.h>
- #include <unistd.h>
- #include <chrono>
- #include <iomanip>
- #include <iostream>
- #include <vector>
- #include <pdx/rpc/argument_encoder.h>
- #include <pdx/rpc/message_buffer.h>
- #include <pdx/rpc/payload.h>
- #include <pdx/utility.h>
- using namespace android::pdx::rpc;
- using namespace android::pdx;
- using std::placeholders::_1;
- using std::placeholders::_2;
- using std::placeholders::_3;
- using std::placeholders::_4;
- using std::placeholders::_5;
- using std::placeholders::_6;
- namespace {
- constexpr size_t kMaxStaticBufferSize = 20480;
- // Provide numpunct facet that formats numbers with ',' as thousands separators.
- class CommaNumPunct : public std::numpunct<char> {
- protected:
- char do_thousands_sep() const override { return ','; }
- std::string do_grouping() const override { return "\03"; }
- };
- class TestPayload : public MessagePayload<SendBuffer>,
- public MessageWriter,
- public MessageReader,
- public NoOpResourceMapper {
- public:
- // MessageWriter
- void* GetNextWriteBufferSection(size_t size) override {
- const size_t section_offset = Size();
- Extend(size);
- return Data() + section_offset;
- }
- OutputResourceMapper* GetOutputResourceMapper() override { return this; }
- // MessageReader
- BufferSection GetNextReadBufferSection() override {
- return {&*ConstCursor(), &*ConstEnd()};
- }
- void ConsumeReadBufferSectionData(const void* new_start) override {
- std::advance(ConstCursor(), PointerDistance(new_start, &*ConstCursor()));
- }
- InputResourceMapper* GetInputResourceMapper() override { return this; }
- };
- class StaticBuffer : public MessageWriter,
- public MessageReader,
- public NoOpResourceMapper {
- public:
- void Clear() {
- read_ptr_ = buffer_;
- write_ptr_ = 0;
- }
- void Rewind() { read_ptr_ = buffer_; }
- // MessageWriter
- void* GetNextWriteBufferSection(size_t size) override {
- void* ptr = buffer_ + write_ptr_;
- write_ptr_ += size;
- return ptr;
- }
- OutputResourceMapper* GetOutputResourceMapper() override { return this; }
- // MessageReader
- BufferSection GetNextReadBufferSection() override {
- return {read_ptr_, std::end(buffer_)};
- }
- void ConsumeReadBufferSectionData(const void* new_start) override {
- read_ptr_ = static_cast<const uint8_t*>(new_start);
- }
- InputResourceMapper* GetInputResourceMapper() override { return this; }
- private:
- uint8_t buffer_[kMaxStaticBufferSize];
- const uint8_t* read_ptr_{buffer_};
- size_t write_ptr_{0};
- };
- // Simple callback function to clear/reset the input/output buffers for
- // serialization. Using raw function pointer here instead of std::function to
- // minimize the overhead of invocation in the tight test loop over millions of
- // iterations.
- using ResetFunc = void(void*);
- // Serialization test function signature, used by the TestRunner.
- using SerializeTestSignature = std::chrono::nanoseconds(MessageWriter* writer,
- size_t iterations,
- ResetFunc* write_reset,
- void* reset_data);
- // Deserialization test function signature, used by the TestRunner.
- using DeserializeTestSignature = std::chrono::nanoseconds(
- MessageReader* reader, MessageWriter* writer, size_t iterations,
- ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data);
- // Generic serialization test runner method. Takes the |value| of type T and
- // serializes it into the output buffer represented by |writer|.
- template <typename T>
- std::chrono::nanoseconds SerializeTestRunner(MessageWriter* writer,
- size_t iterations,
- ResetFunc* write_reset,
- void* reset_data, const T& value) {
- auto start = std::chrono::high_resolution_clock::now();
- for (size_t i = 0; i < iterations; i++) {
- write_reset(reset_data);
- Serialize(value, writer);
- }
- auto stop = std::chrono::high_resolution_clock::now();
- return stop - start;
- }
- // Generic deserialization test runner method. Takes the |value| of type T and
- // temporarily serializes it into the output buffer, then repeatedly
- // deserializes the data back from that buffer.
- template <typename T>
- std::chrono::nanoseconds DeserializeTestRunner(
- MessageReader* reader, MessageWriter* writer, size_t iterations,
- ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data,
- const T& value) {
- write_reset(reset_data);
- Serialize(value, writer);
- T output_data;
- auto start = std::chrono::high_resolution_clock::now();
- for (size_t i = 0; i < iterations; i++) {
- read_reset(reset_data);
- Deserialize(&output_data, reader);
- }
- auto stop = std::chrono::high_resolution_clock::now();
- if (output_data != value)
- return start - stop; // Return negative value to indicate error.
- return stop - start;
- }
- // Special version of SerializeTestRunner that doesn't perform any serialization
- // but does all the same setup steps and moves data of size |data_size| into
- // the output buffer. Useful to determine the baseline to calculate time used
- // just for serialization layer.
- std::chrono::nanoseconds SerializeBaseTest(MessageWriter* writer,
- size_t iterations,
- ResetFunc* write_reset,
- void* reset_data, size_t data_size) {
- std::vector<uint8_t> dummy_data(data_size);
- auto start = std::chrono::high_resolution_clock::now();
- for (size_t i = 0; i < iterations; i++) {
- write_reset(reset_data);
- memcpy(writer->GetNextWriteBufferSection(dummy_data.size()),
- dummy_data.data(), dummy_data.size());
- }
- auto stop = std::chrono::high_resolution_clock::now();
- return stop - start;
- }
- // Special version of DeserializeTestRunner that doesn't perform any
- // deserialization but invokes Rewind on the input buffer repeatedly.
- // Useful to determine the baseline to calculate time used just for
- // deserialization layer.
- std::chrono::nanoseconds DeserializeBaseTest(
- MessageReader* reader, MessageWriter* writer, size_t iterations,
- ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data,
- size_t data_size) {
- std::vector<uint8_t> dummy_data(data_size);
- write_reset(reset_data);
- memcpy(writer->GetNextWriteBufferSection(dummy_data.size()),
- dummy_data.data(), dummy_data.size());
- auto start = std::chrono::high_resolution_clock::now();
- for (size_t i = 0; i < iterations; i++) {
- read_reset(reset_data);
- auto section = reader->GetNextReadBufferSection();
- memcpy(dummy_data.data(), section.first, dummy_data.size());
- reader->ConsumeReadBufferSectionData(
- AdvancePointer(section.first, dummy_data.size()));
- }
- auto stop = std::chrono::high_resolution_clock::now();
- return stop - start;
- }
- // The main class that accumulates individual tests to be executed.
- class TestRunner {
- public:
- struct BufferInfo {
- BufferInfo(const std::string& buffer_name, MessageReader* reader,
- MessageWriter* writer, ResetFunc* read_reset_func,
- ResetFunc* write_reset_func, void* reset_data)
- : name{buffer_name},
- reader{reader},
- writer{writer},
- read_reset_func{read_reset_func},
- write_reset_func{write_reset_func},
- reset_data{reset_data} {}
- std::string name;
- MessageReader* reader;
- MessageWriter* writer;
- ResetFunc* read_reset_func;
- ResetFunc* write_reset_func;
- void* reset_data;
- };
- void AddTestFunc(const std::string& name,
- std::function<SerializeTestSignature> serialize_test,
- std::function<DeserializeTestSignature> deserialize_test,
- size_t data_size) {
- tests_.emplace_back(name, std::move(serialize_test),
- std::move(deserialize_test), data_size);
- }
- template <typename T>
- void AddSerializationTest(const std::string& name, T&& value) {
- const size_t data_size = GetSerializedSize(value);
- auto serialize_test =
- std::bind(static_cast<std::chrono::nanoseconds (*)(
- MessageWriter*, size_t, ResetFunc*, void*, const T&)>(
- &SerializeTestRunner),
- _1, _2, _3, _4, std::forward<T>(value));
- tests_.emplace_back(name, std::move(serialize_test),
- std::function<DeserializeTestSignature>{}, data_size);
- }
- template <typename T>
- void AddDeserializationTest(const std::string& name, T&& value) {
- const size_t data_size = GetSerializedSize(value);
- auto deserialize_test =
- std::bind(static_cast<std::chrono::nanoseconds (*)(
- MessageReader*, MessageWriter*, size_t, ResetFunc*,
- ResetFunc*, void*, const T&)>(&DeserializeTestRunner),
- _1, _2, _3, _4, _5, _6, std::forward<T>(value));
- tests_.emplace_back(name, std::function<SerializeTestSignature>{},
- std::move(deserialize_test), data_size);
- }
- template <typename T>
- void AddTest(const std::string& name, T&& value) {
- const size_t data_size = GetSerializedSize(value);
- if (data_size > kMaxStaticBufferSize) {
- std::cerr << "Test '" << name << "' requires " << data_size
- << " bytes in the serialization buffer but only "
- << kMaxStaticBufferSize << " are available." << std::endl;
- exit(1);
- }
- auto serialize_test =
- std::bind(static_cast<std::chrono::nanoseconds (*)(
- MessageWriter*, size_t, ResetFunc*, void*, const T&)>(
- &SerializeTestRunner),
- _1, _2, _3, _4, value);
- auto deserialize_test =
- std::bind(static_cast<std::chrono::nanoseconds (*)(
- MessageReader*, MessageWriter*, size_t, ResetFunc*,
- ResetFunc*, void*, const T&)>(&DeserializeTestRunner),
- _1, _2, _3, _4, _5, _6, std::forward<T>(value));
- tests_.emplace_back(name, std::move(serialize_test),
- std::move(deserialize_test), data_size);
- }
- std::string CenterString(std::string text, size_t column_width) {
- if (text.size() < column_width) {
- text = std::string((column_width - text.size()) / 2, ' ') + text;
- }
- return text;
- }
- void RunTests(size_t iteration_count,
- const std::vector<BufferInfo>& buffers) {
- using float_seconds = std::chrono::duration<double>;
- const std::string name_column_separator = " : ";
- const std::string buffer_column_separator = " || ";
- const std::string buffer_timing_column_separator = " | ";
- const size_t data_size_column_width = 6;
- const size_t time_column_width = 9;
- const size_t qps_column_width = 18;
- const size_t buffer_column_width = time_column_width +
- buffer_timing_column_separator.size() +
- qps_column_width;
- auto compare_name_length = [](const TestEntry& t1, const TestEntry& t2) {
- return t1.name.size() < t2.name.size();
- };
- auto test_with_longest_name =
- std::max_element(tests_.begin(), tests_.end(), compare_name_length);
- size_t name_column_width = test_with_longest_name->name.size();
- size_t total_width =
- name_column_width + name_column_separator.size() +
- data_size_column_width + buffer_column_separator.size() +
- buffers.size() * (buffer_column_width + buffer_column_separator.size());
- const std::string dbl_separator(total_width, '=');
- const std::string separator(total_width, '-');
- auto print_header = [&](const std::string& header) {
- std::cout << dbl_separator << std::endl;
- std::stringstream ss;
- ss.imbue(std::locale(ss.getloc(), new CommaNumPunct));
- ss << header << " (" << iteration_count << " iterations)";
- std::cout << CenterString(ss.str(), total_width) << std::endl;
- std::cout << dbl_separator << std::endl;
- std::cout << std::setw(name_column_width) << "Test Name" << std::left
- << name_column_separator << std::setw(data_size_column_width)
- << CenterString("Size", data_size_column_width)
- << buffer_column_separator;
- for (const auto& buffer_info : buffers) {
- std::cout << std::setw(buffer_column_width)
- << CenterString(buffer_info.name, buffer_column_width)
- << buffer_column_separator;
- }
- std::cout << std::endl;
- std::cout << std::setw(name_column_width) << "" << name_column_separator
- << std::setw(data_size_column_width)
- << CenterString("bytes", data_size_column_width)
- << buffer_column_separator << std::left;
- for (size_t i = 0; i < buffers.size(); i++) {
- std::cout << std::setw(time_column_width)
- << CenterString("Time, s", time_column_width)
- << buffer_timing_column_separator
- << std::setw(qps_column_width)
- << CenterString("QPS", qps_column_width)
- << buffer_column_separator;
- }
- std::cout << std::right << std::endl;
- std::cout << separator << std::endl;
- };
- print_header("Serialization benchmarks");
- for (const auto& test : tests_) {
- if (test.serialize_test) {
- std::cout << std::setw(name_column_width) << test.name << " : "
- << std::setw(data_size_column_width) << test.data_size
- << buffer_column_separator;
- for (const auto& buffer_info : buffers) {
- auto seconds =
- std::chrono::duration_cast<float_seconds>(test.serialize_test(
- buffer_info.writer, iteration_count,
- buffer_info.write_reset_func, buffer_info.reset_data));
- double qps = iteration_count / seconds.count();
- std::cout << std::fixed << std::setprecision(3)
- << std::setw(time_column_width) << seconds.count()
- << buffer_timing_column_separator
- << std::setw(qps_column_width) << qps
- << buffer_column_separator;
- }
- std::cout << std::endl;
- }
- }
- print_header("Deserialization benchmarks");
- for (const auto& test : tests_) {
- if (test.deserialize_test) {
- std::cout << std::setw(name_column_width) << test.name << " : "
- << std::setw(data_size_column_width) << test.data_size
- << buffer_column_separator;
- for (const auto& buffer_info : buffers) {
- auto seconds =
- std::chrono::duration_cast<float_seconds>(test.deserialize_test(
- buffer_info.reader, buffer_info.writer, iteration_count,
- buffer_info.read_reset_func, buffer_info.write_reset_func,
- buffer_info.reset_data));
- double qps = iteration_count / seconds.count();
- std::cout << std::fixed << std::setprecision(3)
- << std::setw(time_column_width) << seconds.count()
- << buffer_timing_column_separator
- << std::setw(qps_column_width) << qps
- << buffer_column_separator;
- }
- std::cout << std::endl;
- }
- }
- std::cout << dbl_separator << std::endl;
- }
- private:
- struct TestEntry {
- TestEntry(const std::string& test_name,
- std::function<SerializeTestSignature> serialize_test,
- std::function<DeserializeTestSignature> deserialize_test,
- size_t data_size)
- : name{test_name},
- serialize_test{std::move(serialize_test)},
- deserialize_test{std::move(deserialize_test)},
- data_size{data_size} {}
- std::string name;
- std::function<SerializeTestSignature> serialize_test;
- std::function<DeserializeTestSignature> deserialize_test;
- size_t data_size;
- };
- std::vector<TestEntry> tests_;
- };
- std::string GenerateContainerName(const std::string& type, size_t count) {
- std::stringstream ss;
- ss << type << "(" << count << ")";
- return ss.str();
- }
- } // anonymous namespace
- int main(int /*argc*/, char** /*argv*/) {
- const size_t iteration_count = 10000000; // 10M iterations.
- TestRunner test_runner;
- std::cout.imbue(std::locale(std::cout.getloc(), new CommaNumPunct));
- // Baseline tests to figure out the overhead of buffer resizing and data
- // transfers.
- for (size_t len : {0, 1, 9, 66, 259}) {
- auto serialize_base_test =
- std::bind(&SerializeBaseTest, _1, _2, _3, _4, len);
- auto deserialize_base_test =
- std::bind(&DeserializeBaseTest, _1, _2, _3, _4, _5, _6, len);
- test_runner.AddTestFunc("--Baseline--", std::move(serialize_base_test),
- std::move(deserialize_base_test), len);
- }
- // Individual serialization/deserialization tests.
- test_runner.AddTest("bool", true);
- test_runner.AddTest("int32_t", 12);
- for (size_t len : {0, 1, 8, 64, 256}) {
- test_runner.AddTest(GenerateContainerName("string", len),
- std::string(len, '*'));
- }
- // Serialization is too slow to handle such large strings, add this test for
- // deserialization only.
- test_runner.AddDeserializationTest(GenerateContainerName("string", 10240),
- std::string(10240, '*'));
- for (size_t len : {0, 1, 8, 64, 256}) {
- std::vector<int32_t> int_vector(len);
- std::iota(int_vector.begin(), int_vector.end(), 0);
- test_runner.AddTest(GenerateContainerName("vector<int32_t>", len),
- std::move(int_vector));
- }
- std::vector<std::string> vector_of_strings = {
- "012345678901234567890123456789", "012345678901234567890123456789",
- "012345678901234567890123456789", "012345678901234567890123456789",
- "012345678901234567890123456789",
- };
- test_runner.AddTest(
- GenerateContainerName("vector<string>", vector_of_strings.size()),
- std::move(vector_of_strings));
- test_runner.AddTest("tuple<int, bool, string, double>",
- std::make_tuple(123, true, std::string{"foobar"}, 1.1));
- for (size_t len : {0, 1, 8, 64}) {
- std::map<int, std::string> test_map;
- for (size_t i = 0; i < len; i++)
- test_map.emplace(i, std::to_string(i));
- test_runner.AddTest(GenerateContainerName("map<int, string>", len),
- std::move(test_map));
- }
- for (size_t len : {0, 1, 8, 64}) {
- std::unordered_map<int, std::string> test_map;
- for (size_t i = 0; i < len; i++)
- test_map.emplace(i, std::to_string(i));
- test_runner.AddTest(
- GenerateContainerName("unordered_map<int, string>", len),
- std::move(test_map));
- }
- // BufferWrapper can't be used with deserialization tests right now because
- // it requires external buffer to be filled in, which is not available.
- std::vector<std::vector<uint8_t>> data_buffers;
- for (size_t len : {0, 1, 8, 64, 256}) {
- data_buffers.emplace_back(len);
- test_runner.AddSerializationTest(
- GenerateContainerName("BufferWrapper<uint8_t*>", len),
- BufferWrapper<uint8_t*>(data_buffers.back().data(),
- data_buffers.back().size()));
- }
- // Various backing buffers to run the tests on.
- std::vector<TestRunner::BufferInfo> buffers;
- Payload buffer;
- buffers.emplace_back("Non-TLS Buffer", &buffer, &buffer,
- [](void* ptr) { static_cast<Payload*>(ptr)->Rewind(); },
- [](void* ptr) { static_cast<Payload*>(ptr)->Clear(); },
- &buffer);
- TestPayload tls_buffer;
- buffers.emplace_back(
- "TLS Buffer", &tls_buffer, &tls_buffer,
- [](void* ptr) { static_cast<TestPayload*>(ptr)->Rewind(); },
- [](void* ptr) { static_cast<TestPayload*>(ptr)->Clear(); }, &tls_buffer);
- StaticBuffer static_buffer;
- buffers.emplace_back(
- "Static Buffer", &static_buffer, &static_buffer,
- [](void* ptr) { static_cast<StaticBuffer*>(ptr)->Rewind(); },
- [](void* ptr) { static_cast<StaticBuffer*>(ptr)->Clear(); },
- &static_buffer);
- // Finally, run all the tests.
- test_runner.RunTests(iteration_count, buffers);
- return 0;
- }
|