chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.16...3.27)
|
||||
|
||||
add_subdirectory(log_benchmark)
|
||||
add_subdirectory(plot_dashboard_stress)
|
||||
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16...3.27)
|
||||
|
||||
file(GLOB LOG_BENCHMARK_SOURCES LIST_DIRECTORIES true ${CMAKE_CURRENT_SOURCE_DIR}/*)
|
||||
|
||||
add_executable(log_benchmark ${LOG_BENCHMARK_SOURCES})
|
||||
rerun_strict_warning_settings(log_benchmark)
|
||||
target_link_libraries(log_benchmark PRIVATE rerun_sdk)
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/// Log a single large batch of points with positions, colors, radii and a splatted string.
|
||||
void run_points3d_large_batch();
|
||||
|
||||
/// Log many individual points (position, color, radius), each with a different timestamp.
|
||||
void run_points3d_many_individual();
|
||||
|
||||
/// Log a few large images.
|
||||
void run_image();
|
||||
|
||||
// ---
|
||||
|
||||
/// Very simple linear congruency "random" number generator to spread out values a bit.
|
||||
inline int64_t lcg(int64_t& lcg_state) {
|
||||
lcg_state = 1140671485 * lcg_state + 128201163 % 16777216;
|
||||
return lcg_state;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <vector>
|
||||
|
||||
#include "benchmarks.hpp"
|
||||
#include "profile_scope.hpp"
|
||||
|
||||
#include <rerun.hpp>
|
||||
|
||||
constexpr size_t IMAGE_DIMENSION = 1024;
|
||||
constexpr size_t IMAGE_CHANNELS = 4;
|
||||
|
||||
// How many times we log the image.
|
||||
// Each time with a single pixel changed.
|
||||
constexpr size_t NUM_LOG_CALLS = 20'000;
|
||||
|
||||
static std::vector<uint8_t> prepare() {
|
||||
PROFILE_FUNCTION();
|
||||
|
||||
std::vector<uint8_t> image(
|
||||
IMAGE_DIMENSION * IMAGE_DIMENSION * IMAGE_CHANNELS,
|
||||
static_cast<uint8_t>(0)
|
||||
);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
static void execute(std::vector<uint8_t> raw_image_data) {
|
||||
PROFILE_FUNCTION();
|
||||
|
||||
rerun::RecordingStream rec("rerun_example_benchmark_image");
|
||||
|
||||
for (size_t i = 0; i < NUM_LOG_CALLS; ++i) {
|
||||
// Change a single pixel of the image data, just to make sure we transmit something different each time.
|
||||
raw_image_data[i] += 1;
|
||||
|
||||
rec.log(
|
||||
"test_image",
|
||||
rerun::Image::from_rgba32(
|
||||
raw_image_data,
|
||||
{
|
||||
IMAGE_DIMENSION,
|
||||
IMAGE_DIMENSION,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void run_image() {
|
||||
PROFILE_FUNCTION();
|
||||
auto input = prepare();
|
||||
execute(std::move(input));
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Simple benchmark suite for logging data.
|
||||
// The goal is to get an estimate for the entire process of logging data,
|
||||
// including serialization and processing by the recording stream.
|
||||
//
|
||||
// Timings are printed out while running, it's recommended to measure process run time to ensure
|
||||
// we account for all startup overheads and have all background threads finish.
|
||||
//
|
||||
// If not specified otherwise, memory recordings are used.
|
||||
//
|
||||
// The data we generate for benchmarking should be:
|
||||
// * minimal overhead to generate
|
||||
// * not homogeneous (arrow, ourselves, or even the compiler might exploit this)
|
||||
// * not trivially optimized out
|
||||
// * not random between runs
|
||||
//
|
||||
// Run all benchmarks using:
|
||||
// ```
|
||||
// pixi run -e cpp cpp-log-benchmark
|
||||
// ```
|
||||
// Or, run a single benchmark using:
|
||||
// ```
|
||||
// pixi run -e cpp cpp-log-benchmark points3d_large_batch
|
||||
// ```
|
||||
//
|
||||
// For better whole-executable timing capture you can also first build the executable and then run:
|
||||
// ```
|
||||
// pixi run -e cpp cpp-build-log-benchmark
|
||||
// ./build/release/tests/cpp/log_benchmark/log_benchmark
|
||||
// ```
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmarks.hpp"
|
||||
|
||||
static const char* ArgPoints3DLargeBatch = "points3d_large_batch";
|
||||
static const char* ArgPoints3DManyIndividual = "points3d_many_individual";
|
||||
static const char* ArgImage = "image";
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#ifndef NDEBUG
|
||||
printf("WARNING: Debug build, timings will be inaccurate!\n");
|
||||
#endif
|
||||
|
||||
std::vector<const char*> benchmarks(argv + 1, argv + argc);
|
||||
if (argc == 1) {
|
||||
benchmarks.push_back(ArgPoints3DLargeBatch);
|
||||
benchmarks.push_back(ArgPoints3DManyIndividual);
|
||||
benchmarks.push_back(ArgImage);
|
||||
}
|
||||
|
||||
for (const auto& benchmark : benchmarks) {
|
||||
if (strcmp(benchmark, ArgPoints3DLargeBatch) == 0) {
|
||||
run_points3d_large_batch();
|
||||
} else if (strcmp(benchmark, ArgPoints3DManyIndividual) == 0) {
|
||||
run_points3d_many_individual();
|
||||
} else if (strcmp(benchmark, ArgImage) == 0) {
|
||||
run_image();
|
||||
} else {
|
||||
printf("Unknown benchmark: %s\n", benchmark);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <utility>
|
||||
|
||||
#include "benchmarks.hpp"
|
||||
#include "points3d_shared.hpp"
|
||||
#include "profile_scope.hpp"
|
||||
|
||||
#include <rerun.hpp>
|
||||
|
||||
constexpr int64_t NUM_POINTS = 50000000;
|
||||
|
||||
static void execute(Point3DInput input) {
|
||||
PROFILE_FUNCTION();
|
||||
|
||||
rerun::RecordingStream rec("rerun_example_benchmark_points3d_large_batch");
|
||||
|
||||
rec.log(
|
||||
"large_batch",
|
||||
rerun::Points3D(input.positions)
|
||||
.with_colors(input.colors)
|
||||
.with_radii(input.radii)
|
||||
.with_labels({input.label})
|
||||
);
|
||||
}
|
||||
|
||||
void run_points3d_large_batch() {
|
||||
PROFILE_FUNCTION();
|
||||
auto input = prepare_points3d(42, NUM_POINTS);
|
||||
execute(std::move(input));
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <utility>
|
||||
|
||||
#include "benchmarks.hpp"
|
||||
#include "points3d_shared.hpp"
|
||||
#include "profile_scope.hpp"
|
||||
|
||||
#include <rerun.hpp>
|
||||
|
||||
constexpr int64_t NUM_POINTS = 1000000;
|
||||
|
||||
static void execute(Point3DInput input) {
|
||||
PROFILE_FUNCTION();
|
||||
|
||||
rerun::RecordingStream rec("rerun_example_benchmark_points3d_many_individual");
|
||||
|
||||
for (size_t i = 0; i < NUM_POINTS; ++i) {
|
||||
rec.set_time_sequence("my_timeline", static_cast<int64_t>(i));
|
||||
rec.log(
|
||||
"large_batch",
|
||||
rerun::Points3D(input.positions[i])
|
||||
.with_colors({input.colors[i]})
|
||||
.with_radii({input.radii[i]})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void run_points3d_many_individual() {
|
||||
PROFILE_FUNCTION();
|
||||
auto input = prepare_points3d(1337, NUM_POINTS);
|
||||
execute(std::move(input));
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "benchmarks.hpp"
|
||||
#include "profile_scope.hpp"
|
||||
|
||||
#include <rerun.hpp>
|
||||
|
||||
struct MyPoint3D {
|
||||
float x, y, z;
|
||||
};
|
||||
|
||||
struct Point3DInput {
|
||||
std::vector<MyPoint3D> positions;
|
||||
std::vector<uint32_t> colors;
|
||||
std::vector<float> radii;
|
||||
std::string label;
|
||||
|
||||
Point3DInput() = default;
|
||||
Point3DInput(Point3DInput&&) = default;
|
||||
};
|
||||
|
||||
inline Point3DInput prepare_points3d(int64_t lcg_state, size_t num_points) {
|
||||
PROFILE_FUNCTION();
|
||||
|
||||
Point3DInput input;
|
||||
input.positions.resize(num_points);
|
||||
for (auto& pos : input.positions) {
|
||||
pos.x = static_cast<float>(lcg(lcg_state));
|
||||
pos.y = static_cast<float>(lcg(lcg_state));
|
||||
pos.z = static_cast<float>(lcg(lcg_state));
|
||||
}
|
||||
input.colors.resize(num_points);
|
||||
for (auto& color : input.colors) {
|
||||
color = static_cast<uint32_t>(lcg(lcg_state));
|
||||
}
|
||||
input.radii.resize(num_points);
|
||||
for (auto& radius : input.radii) {
|
||||
radius = static_cast<float>(lcg(lcg_state));
|
||||
}
|
||||
input.label = "some label";
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
// TODO(andreas): We want this adapter in rerun, ideally in a generated manner.
|
||||
// Can we do something like a `binary compatible` attribute on fbs that will generate this as well as ctors?
|
||||
template <>
|
||||
struct rerun::CollectionAdapter<rerun::Color, std::vector<uint32_t>> {
|
||||
Collection<Color> operator()(const std::vector<uint32_t>& container) {
|
||||
return Collection<Color>::borrow(container.data(), container.size());
|
||||
}
|
||||
|
||||
Collection<Color> operator()(std::vector<uint32_t>&&) {
|
||||
throw std::runtime_error("Not implemented for temporaries");
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct rerun::CollectionAdapter<rerun::Position3D, std::vector<MyPoint3D>> {
|
||||
Collection<rerun::Position3D> operator()(const std::vector<MyPoint3D>& container) {
|
||||
return Collection<rerun::Position3D>::borrow(container.data(), container.size());
|
||||
}
|
||||
|
||||
Collection<rerun::Position3D> operator()(std::vector<MyPoint3D>&&) {
|
||||
throw std::runtime_error("Not implemented for temporaries");
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct rerun::CollectionAdapter<rerun::Position3D, MyPoint3D> {
|
||||
Collection<rerun::Position3D> operator()(const MyPoint3D& single) {
|
||||
return Collection<rerun::Position3D>::borrow(&single, 1);
|
||||
}
|
||||
|
||||
Collection<rerun::Position3D> operator()(MyPoint3D&&) {
|
||||
throw std::runtime_error("Not implemented for temporaries");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "profile_scope.hpp"
|
||||
|
||||
int ProfileScope::_indentation = 0;
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
/// Simplistic RAII scope for additional profiling.
|
||||
///
|
||||
/// All inlined on purpose.
|
||||
/// Not threadsafe due to indentation!
|
||||
class ProfileScope {
|
||||
public:
|
||||
// std::source_location would be nice here, but it's not widely enough supported
|
||||
// ProfileScope(const std::source_location& location = std::source_location::current())
|
||||
|
||||
ProfileScope(const char* location)
|
||||
: _start(std::chrono::high_resolution_clock::now()), _location(location) {
|
||||
print_indent();
|
||||
printf("%s start …\n", _location);
|
||||
++_indentation;
|
||||
}
|
||||
|
||||
~ProfileScope() {
|
||||
const auto end = std::chrono::high_resolution_clock::now();
|
||||
const auto duration =
|
||||
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - _start);
|
||||
--_indentation;
|
||||
print_indent();
|
||||
printf("%s end: %.2fms\n", _location, duration.count());
|
||||
}
|
||||
|
||||
private:
|
||||
static void print_indent() {
|
||||
for (int i = 0; i < _indentation; ++i) {
|
||||
printf("--");
|
||||
}
|
||||
if (_indentation > 0) {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point _start;
|
||||
const char* _location;
|
||||
static int _indentation;
|
||||
};
|
||||
|
||||
// Quick and dirty macro to profile a function.
|
||||
#define PROFILE_FUNCTION() ProfileScope _function_profile_scope(__FUNCTION__)
|
||||
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16...3.27)
|
||||
|
||||
file(GLOB PLOT_DASHBOARD_STRESS_SOURCES LIST_DIRECTORIES true ${CMAKE_CURRENT_SOURCE_DIR}/*)
|
||||
|
||||
add_executable(plot_dashboard_stress ${PLOT_DASHBOARD_STRESS_SOURCES})
|
||||
rerun_strict_warning_settings(plot_dashboard_stress)
|
||||
target_link_libraries(plot_dashboard_stress PRIVATE rerun_sdk)
|
||||
@@ -0,0 +1,269 @@
|
||||
// Plot dashboard stress test.
|
||||
//
|
||||
// Usage:
|
||||
// ```text
|
||||
// pixi run -e cpp cpp-plot-dashboard --help
|
||||
// ```
|
||||
//
|
||||
// Example:
|
||||
// ```text
|
||||
// pixi run -e cpp cpp-plot-dashboard --num-plots 10 --num-series-per-plot 5 --num-points-per-series 5000 --freq 1000
|
||||
// ```
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <rerun.hpp>
|
||||
#include <rerun/demo_utils.hpp>
|
||||
#include <rerun/third_party/cxxopts.hpp>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const auto rec = rerun::RecordingStream("rerun_example_plot_dashboard_stress");
|
||||
|
||||
cxxopts::Options options("plot_dashboard_stress", "Plot dashboard stress test");
|
||||
|
||||
// clang-format off
|
||||
options.add_options()
|
||||
("h,help", "Print usage")
|
||||
// Rerun
|
||||
("spawn", "Start a new Rerun Viewer process and feed it data in real-time")
|
||||
("connect", "Connects and sends the logged data to a remote Rerun viewer")
|
||||
("save", "Log data to an rrd file", cxxopts::value<std::string>())
|
||||
("stdout", "Log data to standard output, to be piped into a Rerun Viewer")
|
||||
// Dashboard
|
||||
("num-plots", "How many different plots?", cxxopts::value<uint64_t>()->default_value("1"))
|
||||
("num-series-per-plot", "How many series in each single plot?", cxxopts::value<uint64_t>()->default_value("1"))
|
||||
("num-points-per-series", "How many points in each single series?", cxxopts::value<uint64_t>()->default_value("100000"))
|
||||
("freq", "Frequency of logging (applies to all series)", cxxopts::value<double>()->default_value("1000.0"))
|
||||
("temporal-batch-size", "Number of rows to include in each log call", cxxopts::value<uint64_t>())
|
||||
("order", "What order to log the data in ('forwards', 'backwards', 'random') (applies to all series).", cxxopts::value<std::string>()->default_value("forwards"))
|
||||
("series-type", "The method used to generate time series ('gaussian-random-walk', 'sin-uniform').", cxxopts::value<std::string>()->default_value("gaussian-random-walk"))
|
||||
;
|
||||
// clang-format on
|
||||
|
||||
auto args = options.parse(argc, argv);
|
||||
|
||||
if (args.count("help")) {
|
||||
std::cout << options.help() << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// TODO(#4602): need common rerun args helper library
|
||||
if (args["spawn"].as<bool>()) {
|
||||
rec.spawn().exit_on_failure();
|
||||
} else if (args["connect"].as<bool>()) {
|
||||
rec.connect_grpc().exit_on_failure();
|
||||
} else if (args["stdout"].as<bool>()) {
|
||||
rec.to_stdout().exit_on_failure();
|
||||
} else if (args.count("save")) {
|
||||
rec.save(args["save"].as<std::string>()).exit_on_failure();
|
||||
} else {
|
||||
rec.spawn().exit_on_failure();
|
||||
}
|
||||
|
||||
const auto num_plots = args["num-plots"].as<uint64_t>();
|
||||
const auto num_series_per_plot = args["num-series-per-plot"].as<uint64_t>();
|
||||
const auto num_points_per_series = args["num-points-per-series"].as<uint64_t>();
|
||||
const auto temporal_batch_size = args.count("temporal-batch-size")
|
||||
? std::optional(args["temporal-batch-size"].as<uint64_t>())
|
||||
: std::nullopt;
|
||||
|
||||
std::vector<std::string> plot_paths;
|
||||
plot_paths.reserve(num_plots);
|
||||
for (uint64_t i = 0; i < num_plots; ++i) {
|
||||
plot_paths.push_back("plot_" + std::to_string(i));
|
||||
}
|
||||
|
||||
std::vector<std::string> series_paths;
|
||||
series_paths.reserve(num_series_per_plot);
|
||||
for (uint64_t i = 0; i < num_series_per_plot; ++i) {
|
||||
series_paths.push_back("series_" + std::to_string(i));
|
||||
}
|
||||
|
||||
const auto freq = args["freq"].as<double>();
|
||||
|
||||
auto time_per_sim_step = 1.0 / freq;
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 rng(rd());
|
||||
std::uniform_real_distribution<double> distr_uniform_pi(0.0, rerun::demo::PI);
|
||||
std::normal_distribution<double> distr_std_normal;
|
||||
|
||||
std::vector<double> sim_times;
|
||||
const auto order = args["order"].as<std::string>();
|
||||
const auto series_type = args["series-type"].as<std::string>();
|
||||
|
||||
if (order == "forwards") {
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(num_points_per_series); ++i) {
|
||||
sim_times.push_back(static_cast<double>(i) * time_per_sim_step);
|
||||
}
|
||||
} else if (order == "backwards") {
|
||||
for (int64_t i = static_cast<int64_t>(num_points_per_series); i > 0; --i) {
|
||||
sim_times.push_back(static_cast<double>(i - 1) * time_per_sim_step);
|
||||
}
|
||||
} else if (order == "random") {
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(num_points_per_series); ++i) {
|
||||
sim_times.push_back(static_cast<double>(i) * time_per_sim_step);
|
||||
}
|
||||
std::shuffle(sim_times.begin(), sim_times.end(), rng);
|
||||
}
|
||||
|
||||
const auto num_series = num_plots * num_series_per_plot;
|
||||
auto time_per_tick = 1.0 / freq;
|
||||
auto scalars_per_tick = num_series;
|
||||
if (temporal_batch_size.has_value()) {
|
||||
time_per_tick *= static_cast<double>(*temporal_batch_size);
|
||||
scalars_per_tick *= *temporal_batch_size;
|
||||
}
|
||||
const auto expected_total_freq = freq * static_cast<double>(num_series);
|
||||
|
||||
std::vector<std::vector<double>> values_per_series;
|
||||
for (uint64_t series_idx = 0; series_idx < num_series; ++series_idx) {
|
||||
std::vector<double> values;
|
||||
|
||||
double value = 0.0;
|
||||
for (uint64_t i = 0; i < num_points_per_series; ++i) {
|
||||
if (series_type == "gaussian-random-walk") {
|
||||
value += distr_std_normal(rng);
|
||||
} else if (series_type == "sin-uniform") {
|
||||
value = distr_uniform_pi(rng);
|
||||
} else {
|
||||
// Just generate random numbers rather than crash
|
||||
value = distr_std_normal(rng);
|
||||
}
|
||||
|
||||
values.push_back(value);
|
||||
}
|
||||
|
||||
values_per_series.push_back(values);
|
||||
}
|
||||
|
||||
std::vector<size_t> offsets;
|
||||
if (temporal_batch_size.has_value()) {
|
||||
// GCC wrongfully thinks that `temporal_batch_size` is uninitialized despite being initialized upon creation.
|
||||
RR_DISABLE_MAYBE_UNINITIALIZED_PUSH
|
||||
for (size_t i = 0; i < num_points_per_series; i += *temporal_batch_size) {
|
||||
offsets.push_back(i);
|
||||
}
|
||||
RR_DISABLE_MAYBE_UNINITIALIZED_POP
|
||||
} else {
|
||||
offsets.resize(sim_times.size());
|
||||
std::iota(offsets.begin(), offsets.end(), 0);
|
||||
}
|
||||
|
||||
uint64_t total_num_scalars = 0;
|
||||
auto total_start_time = std::chrono::high_resolution_clock::now();
|
||||
double max_load = 0.0;
|
||||
|
||||
auto tick_start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
size_t time_step = 0;
|
||||
for (auto offset : offsets) {
|
||||
std::optional<rerun::TimeColumn> time_column;
|
||||
if (temporal_batch_size.has_value()) {
|
||||
time_column = rerun::TimeColumn::from_duration_secs(
|
||||
"sim_time",
|
||||
rerun::borrow(sim_times.data() + offset, *temporal_batch_size),
|
||||
rerun::SortingStatus::Sorted
|
||||
);
|
||||
} else {
|
||||
rec.set_time_duration_secs("sim_time", sim_times[offset]);
|
||||
}
|
||||
|
||||
// Log
|
||||
|
||||
for (size_t plot_idx = 0; plot_idx < plot_paths.size(); ++plot_idx) {
|
||||
auto global_plot_idx = plot_idx * num_series_per_plot;
|
||||
|
||||
for (size_t series_idx = 0; series_idx < series_paths.size(); ++series_idx) {
|
||||
auto path = plot_paths[plot_idx] + "/" + series_paths[series_idx];
|
||||
const auto& series_values = values_per_series[global_plot_idx + series_idx];
|
||||
if (temporal_batch_size.has_value()) {
|
||||
rec.send_columns(
|
||||
path,
|
||||
*time_column,
|
||||
rerun::Scalars(
|
||||
rerun::borrow(series_values.data() + time_step, *temporal_batch_size)
|
||||
)
|
||||
.columns()
|
||||
);
|
||||
} else {
|
||||
rec.log(path, rerun::Scalars(series_values[time_step]));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (temporal_batch_size.has_value()) {
|
||||
time_step += *temporal_batch_size;
|
||||
} else {
|
||||
++time_step;
|
||||
}
|
||||
|
||||
// Measure how long this took and how high the load was.
|
||||
|
||||
auto elapsed = std::chrono::high_resolution_clock::now() - tick_start_time;
|
||||
max_load = std::max(
|
||||
max_load,
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(elapsed).count() /
|
||||
time_per_tick
|
||||
);
|
||||
|
||||
// Throttle
|
||||
|
||||
auto sleep_duration = std::chrono::duration<double>(time_per_tick) - elapsed;
|
||||
if (sleep_duration.count() > 0.0) {
|
||||
auto sleep_start_time = std::chrono::high_resolution_clock::now();
|
||||
std::this_thread::sleep_for(sleep_duration);
|
||||
auto sleep_elapsed = std::chrono::high_resolution_clock::now() - sleep_start_time;
|
||||
|
||||
// We will very likely be put to sleep for more than we asked for, and therefore need
|
||||
// to pay off that debt in order to meet our frequency goal.
|
||||
auto sleep_debt = sleep_elapsed - sleep_duration;
|
||||
tick_start_time = std::chrono::high_resolution_clock::now() -
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(sleep_debt);
|
||||
} else {
|
||||
tick_start_time = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
// Progress report
|
||||
//
|
||||
// Must come after throttle since we report every wall-clock second:
|
||||
// If ticks are large & fast, then after each send we run into throttle.
|
||||
// So if this was before throttle, we'd not report the first tick no matter how large it was.
|
||||
|
||||
total_num_scalars += scalars_per_tick;
|
||||
auto total_elapsed = std::chrono::high_resolution_clock::now() - total_start_time;
|
||||
if (total_elapsed >= std::chrono::seconds(1)) {
|
||||
double total_elapsed_secs =
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(total_elapsed).count();
|
||||
std::cout << "logged " << total_num_scalars << " scalars over " << total_elapsed_secs
|
||||
<< "s (freq=" << static_cast<double>(total_num_scalars) / total_elapsed_secs
|
||||
<< "Hz, expected=" << expected_total_freq << "Hz, load=" << max_load * 100.0
|
||||
<< "%)" << std::endl;
|
||||
|
||||
auto elapsed_debt = std::chrono::duration<double>(
|
||||
total_elapsed_secs - floor(total_elapsed_secs)
|
||||
); // just keep the fractional part
|
||||
total_start_time = std::chrono::high_resolution_clock::now() -
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed_debt);
|
||||
total_num_scalars = 0;
|
||||
max_load = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (total_num_scalars > 0) {
|
||||
auto total_elapsed = std::chrono::high_resolution_clock::now() - total_start_time;
|
||||
double total_elapsed_secs =
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(total_elapsed).count();
|
||||
std::cout << "logged " << total_num_scalars << " scalars over " << total_elapsed_secs
|
||||
<< "s (freq=" << static_cast<double>(total_num_scalars) / total_elapsed_secs
|
||||
<< "Hz, expected=" << expected_total_freq << "Hz, load=" << max_load * 100.0
|
||||
<< "%)" << std::endl;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user