aboutsummaryrefslogtreecommitdiffstats
path: root/host/tests/streamer_benchmark.cpp
diff options
context:
space:
mode:
authorCiro Nishiguchi <ciro.nishiguchi@ni.com>2019-08-27 16:20:25 -0500
committerMartin Braun <martin.braun@ettus.com>2019-11-26 11:49:45 -0800
commit1a6368331bf441290b0d08ac233c9e5050021493 (patch)
tree30df2489aa86a6504a679e524929f29a08927874 /host/tests/streamer_benchmark.cpp
parent98209df92ea2a6abf6ed41af03bc3909b8e152b9 (diff)
downloaduhd-1a6368331bf441290b0d08ac233c9e5050021493.tar.gz
uhd-1a6368331bf441290b0d08ac233c9e5050021493.tar.bz2
uhd-1a6368331bf441290b0d08ac233c9e5050021493.zip
tests: Add benchmark of streamer and chdr xports
Diffstat (limited to 'host/tests/streamer_benchmark.cpp')
-rw-r--r--host/tests/streamer_benchmark.cpp507
1 files changed, 507 insertions, 0 deletions
diff --git a/host/tests/streamer_benchmark.cpp b/host/tests/streamer_benchmark.cpp
new file mode 100644
index 000000000..46ba6add0
--- /dev/null
+++ b/host/tests/streamer_benchmark.cpp
@@ -0,0 +1,507 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "../common/mock_link.hpp"
+#include <uhd/utils/safe_main.hpp>
+#include <uhdlib/rfnoc/chdr_rx_data_xport.hpp>
+#include <uhdlib/rfnoc/chdr_tx_data_xport.hpp>
+#include <uhdlib/rfnoc/chdr_types.hpp>
+#include <uhdlib/transport/inline_io_service.hpp>
+#include <uhdlib/transport/rx_streamer_impl.hpp>
+#include <uhdlib/transport/tx_streamer_impl.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/program_options.hpp>
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+namespace po = boost::program_options;
+using namespace uhd;
+using namespace uhd::rfnoc;
+using namespace uhd::transport;
+
+static const double TICK_RATE = 100e6;
+static const double SAMP_RATE = 10e6;
+static const size_t FRAME_SIZE = 1000;
+static const double SCALE_FACTOR = 2;
+
+/*!
+ * Mock rx data xport which doesn't do anything, doesn't use I/O services or
+ * links, to benchmark rx_streamer_impl with minimal other overhead.
+ */
+class mock_rx_data_xport
+{
+public:
+ using uptr = std::unique_ptr<mock_rx_data_xport>;
+
+ struct buff_t
+ {
+ using uptr = std::unique_ptr<buff_t>;
+ std::vector<uint8_t> data;
+ };
+
+ struct packet_info_t
+ {
+ bool eob = false;
+ bool has_tsf = false;
+ uint64_t tsf = 0;
+ size_t payload_bytes = 0;
+ const void* payload = nullptr;
+ };
+
+ mock_rx_data_xport(const size_t buff_size) : _buff_size(buff_size)
+ {
+ _buff = std::make_unique<buff_t>();
+ _buff->data.resize(buff_size);
+
+ _packet_info.has_tsf = true;
+ _packet_info.tsf = 1000;
+ _packet_info.payload_bytes = buff_size;
+ _packet_info.payload = _buff->data.data();
+ }
+
+ std::tuple<buff_t::uptr, packet_info_t, bool> get_recv_buff(
+ const int32_t /*timeout_ms*/)
+ {
+ const bool seq_error = false;
+
+ return std::make_tuple(std::move(_buff), _packet_info, seq_error);
+ }
+
+ void release_recv_buff(buff_t::uptr buff)
+ {
+ _buff = std::move(buff);
+ }
+
+ size_t get_max_payload_size() const
+ {
+ return _buff_size;
+ }
+
+private:
+ size_t _buff_size;
+ buff_t::uptr _buff;
+ packet_info_t _packet_info;
+};
+
+/*!
+ * Mock tx data xport which doesn't do anything, doesn't use I/O services or
+ * links, to benchmark tx_streamer_impl with minimal other overhead.
+ */
+class mock_tx_data_xport
+{
+public:
+ using uptr = std::unique_ptr<mock_tx_data_xport>;
+
+ struct buff_t
+ {
+ using uptr = std::unique_ptr<buff_t>;
+ std::vector<uint8_t> data;
+
+ void set_packet_size(const size_t) {}
+ };
+
+ struct packet_info_t
+ {
+ bool eob = false;
+ bool has_tsf = false;
+ uint64_t tsf = 0;
+ size_t payload_bytes = 0;
+ };
+
+ mock_tx_data_xport(const size_t buff_size) : _buff_size(buff_size)
+ {
+ _buff = std::make_unique<buff_t>();
+ _buff->data.resize(buff_size);
+ }
+
+ buff_t::uptr get_send_buff(const int32_t /*timeout_ms*/)
+ {
+ return std::move(_buff);
+ }
+
+ std::pair<void*, size_t> write_packet_header(
+ buff_t::uptr& buff, const packet_info_t& info)
+ {
+ uint8_t* data = buff->data.data();
+ *(reinterpret_cast<packet_info_t*>(data)) = info;
+ return std::make_pair(data + sizeof(info), sizeof(info) + info.payload_bytes);
+ }
+
+ void release_send_buff(buff_t::uptr buff)
+ {
+ _buff = std::move(buff);
+ }
+
+ size_t get_max_payload_size() const
+ {
+ return _buff_size - sizeof(packet_info_t);
+ }
+
+private:
+ size_t _buff_size;
+ buff_t::uptr _buff;
+};
+
+/*!
+ * Mock rx streamer, configured to ignore sequence errors
+ */
+template <typename xport>
+class mock_rx_streamer : public rx_streamer_impl<xport, true>
+{
+public:
+ mock_rx_streamer(const size_t num_chans, const uhd::stream_args_t& stream_args)
+ : rx_streamer_impl<xport, true>(num_chans, stream_args)
+ {
+ }
+
+ void set_tick_rate(double rate)
+ {
+ rx_streamer_impl<xport, true>::set_tick_rate(rate);
+ }
+
+ void set_samp_rate(double rate)
+ {
+ rx_streamer_impl<xport, true>::set_samp_rate(rate);
+ }
+
+ void set_scale_factor(const size_t chan, const double scale_factor)
+ {
+ rx_streamer_impl<xport, true>::set_scale_factor(chan, scale_factor);
+ }
+
+ void issue_stream_cmd(const stream_cmd_t& /*stream_cmd*/) {}
+};
+
+/*!
+ * Mock tx streamer
+ */
+template <typename xport>
+class mock_tx_streamer : public tx_streamer_impl<xport>
+{
+public:
+ mock_tx_streamer(const size_t num_chans, const uhd::stream_args_t& stream_args)
+ : tx_streamer_impl<xport>(num_chans, stream_args)
+ {
+ }
+
+ void set_tick_rate(double rate)
+ {
+ tx_streamer_impl<xport>::set_tick_rate(rate);
+ }
+
+ void set_samp_rate(double rate)
+ {
+ tx_streamer_impl<xport>::set_samp_rate(rate);
+ }
+
+ void set_scale_factor(const size_t chan, const double scale_factor)
+ {
+ tx_streamer_impl<xport>::set_scale_factor(chan, scale_factor);
+ }
+
+ bool recv_async_msg(
+ uhd::async_metadata_t& /*async_metadata*/, double /*timeout = 0.1*/)
+ {
+ return false;
+ }
+};
+
+using rx_streamer_mock_xport = mock_rx_streamer<mock_rx_data_xport>;
+using tx_streamer_mock_xport = mock_tx_streamer<mock_tx_data_xport>;
+using rx_streamer_mock_link = mock_rx_streamer<chdr_rx_data_xport>;
+using tx_streamer_mock_link = mock_tx_streamer<chdr_tx_data_xport>;
+
+/*!
+ * Helper functions
+ */
+static boost::shared_ptr<rx_streamer_mock_xport> make_rx_streamer_mock_xport(
+ const size_t spp, const std::string& format)
+{
+ const uhd::stream_args_t stream_args(format, "sc16");
+ auto streamer = boost::make_shared<rx_streamer_mock_xport>(1, stream_args);
+ streamer->set_tick_rate(TICK_RATE);
+ streamer->set_samp_rate(SAMP_RATE);
+ streamer->set_scale_factor(0, SCALE_FACTOR);
+
+ const size_t bpi = convert::get_bytes_per_item(format);
+ const size_t frame_size = bpi * spp;
+
+ auto xport = std::make_unique<mock_rx_data_xport>(frame_size);
+
+ streamer->connect_channel(0, std::move(xport));
+
+ return streamer;
+}
+
+static boost::shared_ptr<tx_streamer_mock_xport> make_tx_streamer_mock_xport(
+ const size_t spp, const std::string& format)
+{
+ const uhd::stream_args_t stream_args(format, "sc16");
+ auto streamer = boost::make_shared<tx_streamer_mock_xport>(1, stream_args);
+ streamer->set_tick_rate(TICK_RATE);
+ streamer->set_samp_rate(SAMP_RATE);
+ streamer->set_scale_factor(0, SCALE_FACTOR);
+
+ const size_t bpi = convert::get_bytes_per_item(format);
+ const size_t frame_size = bpi * spp + sizeof(mock_tx_data_xport::packet_info_t);
+
+ auto xport = std::make_unique<mock_tx_data_xport>(frame_size);
+
+ streamer->connect_channel(0, std::move(xport));
+
+ return streamer;
+}
+
+static boost::shared_ptr<rx_streamer_mock_link> make_rx_streamer_mock_link(
+ const size_t spp, const std::string& format)
+{
+ const uhd::stream_args_t stream_args(format, "sc16");
+ auto streamer = boost::make_shared<rx_streamer_mock_link>(1, stream_args);
+ streamer->set_tick_rate(TICK_RATE);
+ streamer->set_samp_rate(SAMP_RATE);
+ streamer->set_scale_factor(0, SCALE_FACTOR);
+
+ const chdr::chdr_packet_factory pkt_factory(CHDR_W_64, ENDIANNESS_BIG);
+ const sep_id_pair_t epids = {0, 1};
+ const stream_buff_params_t buff_capacity = {UINT64_MAX, UINT32_MAX};
+ const stream_buff_params_t fc_freq = {UINT64_MAX, UINT32_MAX};
+ const chdr_rx_data_xport::fc_params_t fc_params{buff_capacity, fc_freq};
+
+ const size_t bpi = convert::get_bytes_per_item(format);
+ const size_t frame_size = bpi * spp + 16;
+
+ const mock_recv_link::link_params recv_params = {frame_size, 1};
+ const mock_send_link::link_params send_params = {frame_size, 1};
+
+ auto recv_link = std::make_shared<mock_recv_link>(recv_params, true);
+ auto send_link = std::make_shared<mock_send_link>(send_params, true);
+
+ boost::shared_array<uint8_t> recv_frame(new uint8_t[frame_size]);
+ auto pkt = pkt_factory.make_generic();
+ chdr::chdr_header header;
+ header.set_pkt_type(chdr::PKT_TYPE_DATA_WITH_TS);
+ header.set_length(frame_size);
+ header.set_dst_epid(epids.second);
+ pkt->refresh(recv_frame.get(), header, 1000 /*tsf*/);
+
+ recv_link->push_back_recv_packet(recv_frame, frame_size);
+
+ auto io_srv = inline_io_service::make();
+ io_srv->attach_recv_link(recv_link);
+ io_srv->attach_send_link(send_link);
+
+ auto xport = std::make_unique<chdr_rx_data_xport>(io_srv,
+ recv_link,
+ send_link,
+ pkt_factory,
+ epids,
+ send_link->get_num_send_frames(),
+ fc_params);
+
+ streamer->connect_channel(0, std::move(xport));
+ return streamer;
+}
+
+static boost::shared_ptr<tx_streamer_mock_link> make_tx_streamer_mock_link(
+ const size_t spp, const std::string& format)
+{
+ const uhd::stream_args_t stream_args(format, "sc16");
+ auto streamer = boost::make_shared<tx_streamer_mock_link>(1, stream_args);
+ streamer->set_tick_rate(TICK_RATE);
+ streamer->set_samp_rate(SAMP_RATE);
+ streamer->set_scale_factor(0, SCALE_FACTOR);
+
+ const chdr::chdr_packet_factory pkt_factory(CHDR_W_64, ENDIANNESS_BIG);
+ const sep_id_pair_t epids = {0, 1};
+ const stream_buff_params_t buff_capacity = {UINT64_MAX, UINT32_MAX};
+ const chdr_tx_data_xport::fc_params_t fc_params{buff_capacity};
+
+ const size_t bpi = convert::get_bytes_per_item(format);
+ const size_t frame_size = bpi * spp + 16;
+
+ const mock_recv_link::link_params recv_params = {frame_size, 1};
+ const mock_send_link::link_params send_params = {frame_size, 1};
+
+ auto recv_link = std::make_shared<mock_recv_link>(recv_params, true);
+ auto send_link = std::make_shared<mock_send_link>(send_params, true);
+
+ auto io_srv = inline_io_service::make();
+ io_srv->attach_recv_link(recv_link);
+ io_srv->attach_send_link(send_link);
+
+ auto xport = std::make_unique<chdr_tx_data_xport>(io_srv,
+ recv_link,
+ send_link,
+ pkt_factory,
+ epids,
+ send_link->get_num_send_frames(),
+ fc_params);
+
+ streamer->connect_channel(0, std::move(xport));
+ return streamer;
+}
+
+/*!
+ * Benchmark of rx streamer
+ */
+void benchmark_rx_streamer(
+ rx_streamer::sptr streamer, const size_t spp, const std::string& format)
+{
+ // Allocate buffer
+ const size_t bpi = convert::get_bytes_per_item(format);
+ std::vector<uint8_t> buffer(spp * bpi);
+ std::vector<void*> buffers;
+ buffers.push_back(buffer.data());
+
+ // Run benchmark
+ uhd::rx_metadata_t md;
+
+ const auto start_time = std::chrono::steady_clock::now();
+ const size_t iterations = 1e7;
+
+ for (size_t i = 0; i < iterations; i++) {
+ streamer->recv(buffers, spp, md, 1.0, true);
+ }
+
+ const auto end_time = std::chrono::steady_clock::now();
+ const std::chrono::duration<double> elapsed_time(end_time - start_time);
+ const double time_per_packet = elapsed_time.count() / iterations;
+
+ std::cout << format << ": " << time_per_packet / spp * 1e9 << " ns/sample, "
+ << time_per_packet * 1e9 << " ns/packet\n";
+}
+
+/*!
+ * Benchmark of tx streamer
+ */
+void benchmark_tx_streamer(tx_streamer::sptr streamer,
+ const size_t spp,
+ const std::string& format,
+ bool use_time_spec)
+{
+ // Allocate buffer
+ const size_t bpi = convert::get_bytes_per_item(format);
+ std::vector<uint8_t> buffer(spp * bpi);
+ std::vector<void*> buffers;
+ buffers.push_back(buffer.data());
+
+ // Run benchmark
+ uhd::tx_metadata_t md;
+ md.has_time_spec = use_time_spec;
+
+ const auto start_time = std::chrono::steady_clock::now();
+ const size_t iterations = 1e7;
+
+ for (size_t i = 0; i < iterations; i++) {
+ if (use_time_spec) {
+ md.time_spec = uhd::time_spec_t(i, 0.0);
+ }
+ streamer->send(buffers, spp, md, 1.0);
+ }
+
+ const auto end_time = std::chrono::steady_clock::now();
+ const std::chrono::duration<double> elapsed_time(end_time - start_time);
+ const double time_per_packet = elapsed_time.count() / iterations;
+
+ std::cout << format << ": " << time_per_packet / spp * 1e9 << " ns/sample, "
+ << time_per_packet * 1e9 << " ns/packet\n";
+}
+
+int UHD_SAFE_MAIN(int argc, char* argv[])
+{
+ po::options_description desc("Allowed options");
+ desc.add_options()("help", "help message");
+
+ po::variables_map vm;
+ po::store(po::parse_command_line(argc, argv, desc), vm);
+ po::notify(vm);
+
+ // Print the help message
+ if (vm.count("help")) {
+ std::cout << boost::format("UHD Streamer Benchmark %s") % desc << std::endl;
+ std::cout << " Benchmark of send and receive streamer functions\n"
+ " All benchmarks use mock transport objects. No\n"
+ " parameters are needed to run this benchmark.\n"
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ const char* formats[] = {"sc16", "fc32", "fc64"};
+ constexpr size_t spp = 1000;
+ std::cout << "spp: " << spp << "\n";
+
+ std::cout << "----------------------------------------------------------\n";
+ std::cout << "Benchmark of recv with mock transport \n";
+ std::cout << " \n";
+ std::cout << " Measures time spent in the rx streamer only. \n";
+ std::cout << "----------------------------------------------------------\n";
+
+ std::cout << "*** with timespec ***\n";
+ for (size_t i = 0; i < std::extent<decltype(formats)>::value; i++) {
+ auto streamer = make_rx_streamer_mock_xport(spp, formats[i]);
+ benchmark_rx_streamer(streamer, spp, formats[i]);
+ }
+
+ std::cout << "\n";
+ std::cout << "----------------------------------------------------------\n";
+ std::cout << "Benchmark of send with mock transport \n";
+ std::cout << " \n";
+ std::cout << " Measures time time spent in the tx streamer only. \n";
+ std::cout << "----------------------------------------------------------\n";
+
+ std::cout << "*** without timespec ***\n";
+ for (size_t i = 0; i < std::extent<decltype(formats)>::value; i++) {
+ auto streamer = make_tx_streamer_mock_xport(spp, formats[i]);
+ benchmark_tx_streamer(streamer, spp, formats[i], false);
+ }
+ std::cout << "\n";
+
+ std::cout << "*** with timespec ***\n";
+ for (size_t i = 0; i < std::extent<decltype(formats)>::value; i++) {
+ auto streamer = make_tx_streamer_mock_xport(spp, formats[i]);
+ benchmark_tx_streamer(streamer, spp, formats[i], true);
+ }
+ std::cout << "\n";
+
+ std::cout << "----------------------------------------------------------\n";
+ std::cout << "Benchmark of recv with mock link \n";
+ std::cout << " \n";
+ std::cout << " Measures time time spent in the rx streamer, I/O \n";
+ std::cout << " service, and chdr data xport. \n";
+ std::cout << "----------------------------------------------------------\n";
+
+ std::cout << "*** with timespec ***\n";
+ for (size_t i = 0; i < std::extent<decltype(formats)>::value; i++) {
+ auto streamer = make_rx_streamer_mock_link(spp, formats[i]);
+ benchmark_rx_streamer(streamer, spp, formats[i]);
+ }
+ std::cout << "\n";
+
+ std::cout << "----------------------------------------------------------\n";
+ std::cout << "Benchmark of send with mock link \n";
+ std::cout << " \n";
+ std::cout << " Measures time time spent in the tx streamer, I/O \n";
+ std::cout << " service, and chdr data xport. \n";
+ std::cout << "----------------------------------------------------------\n";
+
+ std::cout << "*** without timespec ***\n";
+ for (size_t i = 0; i < std::extent<decltype(formats)>::value; i++) {
+ auto streamer = make_tx_streamer_mock_link(spp, formats[i]);
+ benchmark_tx_streamer(streamer, spp, formats[i], false);
+ }
+ std::cout << "\n";
+
+ std::cout << "*** with timespec ***\n";
+ for (size_t i = 0; i < std::extent<decltype(formats)>::value; i++) {
+ auto streamer = make_tx_streamer_mock_link(spp, formats[i]);
+ benchmark_tx_streamer(streamer, spp, formats[i], true);
+ }
+ std::cout << "\n";
+
+ return EXIT_SUCCESS;
+}