Search code examples
c++boost-asio

Boost Asio experimental channel poor performance


I wrote the following code to analyze the performance of the experimental channel in Asio within a single-threaded application. On [email protected], it takes around 1 second to complete, demonstrating a throughput of approximately 3M items per second.

The problem might be due to the fact that asio is in single-threaded mode, causing the producer to signal the consumer part and leading to immediate resumption of the consumer coroutine on every call to async_send(). However, I'm unsure how to test to confirm if this is the case and how to avoid it in real applications. Reducing the channel buffer size, even to 0, has no effect on the throughput, which might be for the same reason.

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/experimental/channel.hpp>

namespace asio = boost::asio;
using namespace asio::experimental::awaitable_operators;
using channel_t = asio::experimental::channel< void(boost::system::error_code, uint64_t) >;

asio::awaitable< void >
producer(channel_t &ch)
{
    for (uint64_t i = 0; i < 3'000'000; i++)
        co_await ch.async_send(boost::system::error_code {}, i, asio::use_awaitable);

    ch.close();
}

asio::awaitable< void >
consumer(channel_t &ch)
{
    for (;;)
        co_await ch.async_receive(asio::use_awaitable);
}

asio::awaitable< void >
experiment()
{
    channel_t ch { co_await asio::this_coro::executor, 1000 };
    co_await (consumer(ch) && producer(ch));
}
int
main()
{
    asio::io_context ctx {};
    asio::co_spawn(ctx, experiment(), asio::detached);
    ctx.run();
}


Solution

  • It turned out that the consumer and producer sides are rescheduled on each send/receive operation; that's why the channel size has no effect on the throughput. I've made the following changes to the code, and now it can send 90M per second. However, this is what I expected from the implementation.

     asio::awaitable< void >
     producer(channel_t &ch)
     {
         for (uint64_t i = 0; i < 90'000'000; i++)
         {
             if (!ch.try_send(boost::system::error_code {}, i))
                 co_await ch.async_send(boost::system::error_code {}, i, asio::use_awaitable);
         }
     
         ch.close();
     }
     
     asio::awaitable< void >
     consumer(channel_t &ch)
     {
         for (;;)
         {
             if (!ch.try_receive([](auto, auto) {}))
                 co_await ch.async_receive(asio::use_awaitable);
         }
     }
    

    I believe the reason why this is not the default behavior of channels is that awaitables in Asio have no way to return true in the await_ready() call, so they always have to suspend and initiate an asynchronous operation.