Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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<int> chan{2};`
* Unbuffered:
* Never blocks writes.
* It blocks the reader threads when channel is empty until a writer thread writes elements.
* `msd::channel<int> chan{};`
* Never blocks writes.
* It blocks the reader threads when channel is empty until a writer thread writes elements.
* `msd::channel<int> 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<int, msd::vector_storage<int>> 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<int, msd::array_storage<int, 10>> chan{};`
* `msd::channel<int, msd::array_storage<int, 10>> chan{10}; // does not compile because capacity is already passed as template argument`
* aka `msd::static_channel<int, 10>`
* `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<int, msd::queue_storage<int>> 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<int, msd::array_storage<int, 10>> chan{};`
* `msd::channel<int, msd::array_storage<int, 10>> chan{10}; // does not compile because capacity is already passed as template argument`
* aka `msd::static_channel<int, 10>`

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.
Expand Down
88 changes: 56 additions & 32 deletions benchmarks/channel_benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,96 @@
#include <benchmark/benchmark.h>

#include <string>
#include <thread>

/**
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 <typename Channel>
std::thread create_producer(Channel& channel)
{
msd::channel<std::string, msd::queue_storage<std::string>> 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<std::string, msd::queue_storage<std::string>> 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<std::string, msd::vector_storage<std::string>> channel{10};

std::string input(1000000, 'x');
std::string out{};
out.resize(input.size());
msd::channel<std::string, msd::vector_storage<std::string>> 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<std::string, msd::array_storage<std::string, 10>> channel{};

std::string input(1000000, 'x');
std::string out{};
out.resize(input.size());
msd::channel<std::string, msd::array_storage<std::string, channel_capacity>> 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);
Expand Down
5 changes: 3 additions & 2 deletions include/msd/channel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T>
using default_storage = queue_storage<T>;
using default_storage = vector_storage<T>;

/**
* @brief Trait to check if a storage type has a static **capacity** member.
Expand All @@ -60,7 +61,7 @@ struct is_static_storage<Storage, decltype((void)Storage::capacity, void())> : 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 <typename T, typename Storage = default_storage<T>>
class channel {
Expand Down