diff --git a/Makefile b/Makefile index 7fcebd6..29c0165 100644 --- a/Makefile +++ b/Makefile @@ -9,15 +9,15 @@ all: bench: - sudo cpupower frequency-set --governor performance - rm -rf $(BENCH_DIR) && mkdir $(BENCH_DIR) && cd $(BENCH_DIR) \ + mkdir -p $(BENCH_DIR) && cd $(BENCH_DIR) \ && cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCPP_CHANNEL_BUILD_BENCHMARKS=ON \ && cmake --build . --config Release --target channel_benchmark -j \ - && ./benchmarks/channel_benchmark + && ./benchmarks/channel_benchmark --benchmark_repetitions=10 --benchmark_report_aggregates_only=true - sudo cpupower frequency-set --governor powersave coverage: - rm -rf $(COV_DIR) && mkdir $(COV_DIR) && cd $(COV_DIR) \ + mkdir -p $(COV_DIR) && cd $(COV_DIR) \ && cmake ../.. -DCMAKE_BUILD_TYPE=Debug -DCPP_CHANNEL_BUILD_TESTS=ON -DCPP_CHANNEL_COVERAGE=ON \ && cmake --build . --config Debug --target channel_tests -j \ && ctest -C Debug --verbose -L channel_tests --output-on-failure -j \ diff --git a/README.md b/README.md index 8a52d41..8cd44bd 100644 --- a/README.md +++ b/README.md @@ -14,29 +14,32 @@ * Uses a customizable `storage` to store elements. It's a class that can be constructed in several ways: + * Buffered: - * The channel accepts a specified number of elements, after which it blocks the writer threads and waits for a reader thread to read an element. - * It blocks the reader threads when channel is empty until a writer thread writes elements. - * `msd::channel chan{2};` + * The channel accepts a specified number of elements, after which it blocks the writer threads and waits for a reader thread to read an element. + * It blocks the reader threads when channel is empty until a writer thread writes elements. + * `msd::channel chan{2};` * Unbuffered: - * Never blocks writes. - * It blocks the reader threads when channel is empty until a writer thread writes elements. - * `msd::channel chan{};` + * Never blocks writes. + * It blocks the reader threads when channel is empty until a writer thread writes elements. + * `msd::channel chan{};` * Heap- or stack-allocated: pass a custom storage or choose a [built-in storage](https://github.com/andreiavrammsd/cpp-channel/blob/master/include/msd/storage.hpp): - * `msd::queue_storage` (default): uses [std::queue](https://en.cppreference.com/w/cpp/container/queue.html) - * `msd::vector_storage`: uses [std::vector](https://en.cppreference.com/w/cpp/container/vector.html) (if cache locality is important) - * `msd::channel> chan{2};` - * `msd::array_storage` (always buffered): uses [std::array](https://en.cppreference.com/w/cpp/container/array.html) (if you want stack allocation) - * `msd::channel> chan{};` - * `msd::channel> chan{10}; // does not compile because capacity is already passed as template argument` - * aka `msd::static_channel` + * `msd::vector_storage` (default): uses [std::vector](https://en.cppreference.com/w/cpp/container/vector.html) + * `msd::queue_storage`: uses [std::queue](https://en.cppreference.com/w/cpp/container/queue.html) + * `msd::channel> chan{2};` + * `msd::array_storage` (always buffered): uses [std::array](https://en.cppreference.com/w/cpp/container/array.html) (if you want stack allocation) + * `msd::channel> chan{};` + * `msd::channel> chan{10}; // does not compile because capacity is already passed as template argument` + * aka `msd::static_channel` A `storage` is: + * A class with a specific interface for storing elements. * Must implement [FIFO](https://en.wikipedia.org/wiki/FIFO) logic. * See [built-in storages](https://github.com/andreiavrammsd/cpp-channel/blob/master/include/msd/storage.hpp). Exceptions: + * msd::operator<< throws `msd::closed_channel` if channel is closed. * `msd::channel::write` returns `bool` status instead of throwing. * Heap-allocated storages could throw. diff --git a/benchmarks/channel_benchmark.cpp b/benchmarks/channel_benchmark.cpp index e5c6422..59de889 100644 --- a/benchmarks/channel_benchmark.cpp +++ b/benchmarks/channel_benchmark.cpp @@ -3,72 +3,96 @@ #include #include +#include /** Results on release build with CPU scaling disabled - c++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 + g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 - 2025-06-13T00:17:30+03:00 - Running ./tests/channel_benchmark - Run on (8 X 3999.91 MHz CPU s) + 2025-06-14T22:36:32+03:00 + Running ./benchmarks/channel_benchmark + Run on (8 X 3999.7 MHz CPU s) CPU Caches: L1 Data 32 KiB (x4) L1 Instruction 32 KiB (x4) L2 Unified 256 KiB (x4) L3 Unified 8192 KiB (x1) - Load Average: 2.65, 1.61, 1.50 - ------------------------------------------------------------------------------ - Benchmark Time CPU Iterations - ------------------------------------------------------------------------------ - channel_with_queue_storage 42602 ns 42598 ns 16407 - channel_with_vector_storage 42724 ns 42723 ns 16288 - channel_with_vector_storage 51332 ns 51328 ns 11776 + Load Average: 1.22, 1.13, 1.21 + ----------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ----------------------------------------------------------------------------- + channel_with_queue_storage_mean 466969277 ns 428207411 ns 10 + channel_with_queue_storage_median 433695208 ns 363801277 ns 10 + channel_with_queue_storage_stddev 72558997 ns 99239885 ns 10 + channel_with_queue_storage_cv 15.54 % 23.18 % 10 + channel_with_vector_storage_mean 461215 ns 405569 ns 10 + channel_with_vector_storage_median 438756 ns 378237 ns 10 + channel_with_vector_storage_stddev 70937 ns 88753 ns 10 + channel_with_vector_storage_cv 15.38 % 21.88 % 10 + channel_with_array_storage_mean 233 ns 221 ns 10 + channel_with_array_storage_median 210 ns 194 ns 10 + channel_with_array_storage_stddev 52.1 ns 57.8 ns 10 + channel_with_array_storage_cv 22.35 % 26.22 % 10 */ -static void channel_with_queue_storage(benchmark::State& state) +template +std::thread create_producer(Channel& channel) { - msd::channel> channel{10}; + return std::thread([&] { + for (size_t i = 0; i < 100000; ++i) { + std::string input(i, 'c'); + channel << std::move(input); + } + channel.close(); + }); +} + +static constexpr std::size_t channel_capacity = 1024; - std::string input(1000000, 'x'); - std::string out{}; - out.resize(input.size()); +static void channel_with_queue_storage(benchmark::State& state) +{ + msd::channel> channel{channel_capacity}; + auto producer = create_producer(channel); for (auto _ : state) { - benchmark::DoNotOptimize(channel << input); - benchmark::DoNotOptimize(channel >> out); + for (auto value : channel) { + benchmark::DoNotOptimize(value); + } } + + producer.join(); } BENCHMARK(channel_with_queue_storage); static void channel_with_vector_storage(benchmark::State& state) { - msd::channel> channel{10}; - - std::string input(1000000, 'x'); - std::string out{}; - out.resize(input.size()); + msd::channel> channel{1024}; + auto producer = create_producer(channel); for (auto _ : state) { - benchmark::DoNotOptimize(channel << input); - benchmark::DoNotOptimize(channel >> out); + for (auto value : channel) { + benchmark::DoNotOptimize(value); + } } + + producer.join(); } BENCHMARK(channel_with_vector_storage); static void channel_with_array_storage(benchmark::State& state) { - msd::channel> channel{}; - - std::string input(1000000, 'x'); - std::string out{}; - out.resize(input.size()); + msd::channel> channel{}; + auto producer = create_producer(channel); for (auto _ : state) { - benchmark::DoNotOptimize(channel << input); - benchmark::DoNotOptimize(channel >> out); + for (auto value : channel) { + benchmark::DoNotOptimize(value); + } } + + producer.join(); } BENCHMARK(channel_with_array_storage); diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index 36f89a3..3367355 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -35,9 +35,10 @@ class closed_channel : public std::runtime_error { * * @tparam T The type of the elements. * @typedef default_storage + * @warning Do not rely on this. Can be changed anytime. */ template -using default_storage = queue_storage; +using default_storage = vector_storage; /** * @brief Trait to check if a storage type has a static **capacity** member. @@ -60,7 +61,7 @@ struct is_static_storage : s * - Includes a blocking input iterator. * * @tparam T The type of the elements. - * @tparam Storage The storage type used to hold the elements. Default: msd::queue_storage. + * @tparam Storage The storage type used to hold the elements. Default: msd::vector_storage. */ template > class channel {