The Sample Utilities Module (sample_utilities.hpp
) provides a set of tools for generating, populating, and serializing test data for performance analysis. It enables the creation of scientifically distributed sample sizes, populates them with data using customizable functions, and exports the results to various file formats.
Key Features
- Intelligent Size Generation: Generates logarithmically distributed sample sizes with configurable bias.
- Type-Safe Data Generation: Uses C++ concepts to ensure type-safe filler functions.
- Multi-Format Serialization: Supports text, CSV, and JSON output formats.
- Flexible API: Designed to be highly flexible with support for custom serialization strategies.
- Automatic Format Detection: Automatically selects the correct serialization format based on the file extension.
Concepts and Type Requirements
The module defines several C++ concepts to enforce type safety and clear requirements for its functions and components.
StreamInsertable
: This concept ensures that a type can be inserted into a standard std::ostream
.
template <typename T>
concept StreamInsertable = requires(std::ostream& os, const T& t) {
{ os << t } -> std::same_as<std::ostream&>;
};
FillerFunction
: This concept validates that a function can populate a std::vector
of a specified type.
template <typename F, typename T>
concept FillerFunction = std::invocable<F, std::vector<T>&, size_t>;
SampleSerializer
: This concept verifies that a serialization function takes a sample and returns a std::string
.
template <typename S, typename Sample>
concept SampleSerializer =
std::invocable<S, const Sample&> && std::is_convertible_v<std::invoke_result_t<S, const Sample&>, std::string>;
Core Components
SampleSizeConfig Structure
The SampleSizeConfig
struct is a simple container used to configure the parameters for generating sample sizes. It allows for rounding and biasing the distribution.
struct SampleSizeConfig
{
size_t round_to = 100;
double bias = 1.0;
};
round_to
: Rounds the generated sizes to the nearest multiple of this value.
bias
: A factor that adjusts the distribution curve; a value greater than 1.0
biases the distribution towards larger sizes, while a value less than 1.0
biases it towards smaller sizes.
generate_sizes Function
The generate_sizes
function creates a std::vector
of scientifically distributed sample sizes, using a logarithmic approach with configurable bias and rounding to ensure a good spread for performance testing.
[[nodiscard]] inline std::vector<size_t>
generate_sizes(size_t sample_count, size_t max_sample_size, const SampleSizeConfig& config = {});
generate_samples Function
The generate_samples
function creates data samples by populating containers based on the sizes generated by generate_sizes
. It accepts a filler function which is responsible for adding data to each sample.
template <typename T, detail::FillerFunction<T> F>
[[nodiscard]] std::vector<std::vector<T>> generate_samples(F&& filler, const std::vector<size_t>& sizes);
serialize_iterable Function
This utility function is a default serializer for iterables that can be inserted into a stream. It formats the data as a string.
template <std::ranges::range Iterable>
requires detail::StreamInsertable<std::ranges::range_value_t<Iterable>>
[[nodiscard]] std::string serialize_iterable(const Iterable& container);
save_samples Function
The save_samples
function exports the generated data samples to a file. It automatically determines the output format based on the file extension (.txt
, .csv
, .json
) and uses a custom serializer to handle the data format.
template <std::ranges::range Container, detail::SampleSerializer<std::ranges::range_value_t<Container>> Serializer>
void save_samples(const Container& samples, Serializer&& serializer, const std::filesystem::path& filename);
There is also a convenience overload that automatically uses serialize_iterable
for stream-insertable types.
template <std::ranges::range Container>
requires detail::StreamInsertable<std::ranges::range_value_t<Container>>
void save_samples(const Container& samples, const std::filesystem::path& filename)
{
save_samples(samples, serialize_iterable<Container>, filename);
}
Error Handling
The module provides explicit error handling for common issues:
- File Errors: Throws
std::runtime_error
if a file cannot be opened.
- Unsupported Formats: Throws
std::runtime_error
for unknown file extensions.
- Empty Containers: Handles empty ranges gracefully during serialization.
Integration with Runtime Analyzer
The Sample Utilities Module is designed to work seamlessly with the Runtime Analyzer and Runtime Reporter modules, forming a complete performance analysis workflow.
auto profile_runtime(Func &&func, const Container &samples, Args &&... args)
Definition runtime_analyzer.hpp:110
void save_reports(const RuntimeProfile< Unit > &runtime_profile, const std::string &base_filename="runtime_report")
Definition runtime_reporter.hpp:94
std::vector< std::vector< T > > generate_samples(F &&filler, const std::vector< size_t > &sizes)
Definition sample_utilities.hpp:147
std::vector< size_t > generate_sizes(size_t sample_count, size_t max_sample_size, const SampleSizeConfig &config={})
Definition sample_utilities.hpp:80
void save_samples(const Container &samples, Serializer &&serializer, const std::filesystem::path &filename)
Definition sample_utilities.hpp:178