Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost asio synchronous vs asynchronous operations performance

Tags:

c++

boost-asio

This is somewhat similar to boost::asio async performance. As there is no conclusive answer to that question, I'm posting a similar question with sample code and stats to demonstrate the problem.

Below, I've sample synchronous and asynchronous server applications, which send 25 byte message to the client in a loop continuously. On the client side, I'm checking at what rate it is able to receive the messages. The sample setup is pretty simple. In synchronous server case, it spawns a new thread per client connection and the thread keeps sending the 25-byte message in a loop. In asynchronous server case as well it spawns a new thread per client connection and the thread keeps sending the 25-byte message in a loop, using asynchronous write (main thread is the one which calls ioservice.run()). For the performance testing I'm using only one client.

Synchronous server code:

#include <iostream>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using boost::asio::ip::tcp;

class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
    typedef boost::shared_ptr<tcp_connection> pointer;

    static pointer create(boost::asio::io_service& io_service)
    {
       return pointer(new tcp_connection(io_service));
    }

    tcp::socket& socket()
    {
        return socket_;
    }

    void start()
    {
        for (;;) {
            try {
                ssize_t len = boost::asio::write(socket_, boost::asio::buffer(message_));
                if (len != message_.length()) {
                    std::cerr<<"Unable to write all the bytes"<<std::endl;
                    break;
                }
                if (len == -1) {
                    std::cerr<<"Remote end closed the connection"<<std::endl;
                    break;
                }
            }
            catch (std::exception& e) {
                std::cerr<<"Error while sending data"<<std::endl;
                break;
            }
        }
    }

private:
    tcp_connection(boost::asio::io_service& io_service)
        : socket_(io_service),
          message_(25, 'A')
    {
    }

    tcp::socket socket_;
    std::string message_;
};

class tcp_server
{
public:
    tcp_server(boost::asio::io_service& io_service)
        : acceptor_(io_service, tcp::endpoint(tcp::v4(), 1234))
    {
        start_accept();
    }

private:
    void start_accept()
    {
        for (;;) {
            tcp_connection::pointer new_connection =
                tcp_connection::create(acceptor_.get_io_service());
            acceptor_.accept(new_connection->socket());
            boost::thread(boost::bind(&tcp_connection::start, new_connection));
        }
    }
    tcp::acceptor acceptor_;
};

int main()
{
    try {
        boost::asio::io_service io_service;
        tcp_server server(io_service);
    }
    catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
    }
    return 0;
}

ASynchronous server code:

#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>

#include <boost/thread.hpp>

using boost::asio::ip::tcp;

class tcp_connection
        : public boost::enable_shared_from_this<tcp_connection>
{
public:
    typedef boost::shared_ptr<tcp_connection> pointer;

    static pointer create(boost::asio::io_service& io_service)
    {
        return pointer(new tcp_connection(io_service));
    }

    tcp::socket& socket()
    {
        return socket_;
    }

    void start()
    {
        while (socket_.is_open()) {
            boost::asio::async_write(socket_, boost::asio::buffer(message_),
                boost::bind(&tcp_connection::handle_write, shared_from_this(),
                            boost::asio::placeholders::error,
                            boost::asio::placeholders::bytes_transferred));
        }
    }

private:
    tcp_connection(boost::asio::io_service& io_service)
        : socket_(io_service),
          message_(25, 'A')
    {
    }

    void handle_write(const boost::system::error_code& error,
                      size_t bytes_transferred)
    {
        if (error) {
            if (socket_.is_open()) {
                std::cout<<"Error while sending data asynchronously"<<std::endl;
                socket_.close();
            }
        }
    }

    tcp::socket socket_;
    std::string message_;
};

class tcp_server
{
public:
    tcp_server(boost::asio::io_service& io_service)
        : acceptor_(io_service, tcp::endpoint(tcp::v4(), 1234))
    {
        start_accept();
    }

private:
    void start_accept()
    {
        tcp_connection::pointer new_connection =
                tcp_connection::create(acceptor_.get_io_service());
        acceptor_.async_accept(new_connection->socket(),
                boost::bind(&tcp_server::handle_accept, this, new_connection,
                        boost::asio::placeholders::error));
    }

    void handle_accept(tcp_connection::pointer new_connection,
                       const boost::system::error_code& error)
    {
        if (!error) {
            boost::thread(boost::bind(&tcp_connection::start, new_connection));
        }

        start_accept();
    }

    tcp::acceptor acceptor_;
};

int main()
{
    try {
        boost::asio::io_service io_service;
        tcp_server server(io_service);
        io_service.run();
    }
    catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

Client code:

#include <iostream>

#include <boost/asio.hpp>
#include <boost/array.hpp>

int main(int argc, char* argv[])
{
    if (argc != 3) {
        std::cerr<<"Usage: client <server-host> <server-port>"<<std::endl;
        return 1;
    }

    boost::asio::io_service io_service;
    boost::asio::ip::tcp::resolver resolver(io_service);
    boost::asio::ip::tcp::resolver::query query(argv[1], argv[2]);
    boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(query);
    boost::asio::ip::tcp::resolver::iterator end;
    boost::asio::ip::tcp::socket socket(io_service);
    boost::asio::connect(socket, it);

//    Statscollector to periodically print received messages stats
//    sample::myboost::StatsCollector stats_collector(5);
//    sample::myboost::StatsCollectorScheduler statsScheduler(stats_collector);
//    statsScheduler.start();

    for (;;) {
        boost::array<char, 25> buf;
        boost::system::error_code error;
        size_t len = socket.read_some(boost::asio::buffer(buf), error);
//        size_t len = boost::asio::read(socket, boost::asio::buffer(buf));
        if (len != buf.size()) {
            std::cerr<<"Length is not "<< buf.size() << " but "<<len<<std::endl;
        }
//        stats_collector.incr_msgs_received();
    }
}

Question:

When the client is running against synchronous server it is able to receive around 700K msgs/sec but when it is running against asynchronous server the performance is dropped to around 100K-120K msgs/sec. I know that one should use asynchronous IO for scalability when we have more number of clients and in the above case as I'm using only a single client, the obvious advantage of asynchronous IO is not evident. But the question is, is asynchronous IO expected to effect the performance so badly for a single client case or am I missing some obvious best practices to follow with asynchronous IO? Is the significant drop in the performance is because of the thread switch between ioservice thread (which is main thread in the above case) and connection thread?

Setup: I'm using BOOST 1.47 on Linux machine.

like image 372
Donald Alan Avatar asked Mar 21 '14 15:03

Donald Alan


1 Answers

This is not how the asynchronous send is supposed to be used: in this way the connection's thread is putting in the asio queue more and more write requests, and concurrently the thread invoking ioservice.run() is dequeuing them.

The low performance is very probably due to the fact that there is high contention on the ioservice work queue on part of the main thread (producer) and the thread running the ioservice (consumer).

Furthermore if you monitor your memory you should see it growing, at the point to block your system eventually: I do expect the producer being faster than the consumer.

The correct approach (untested), reporting only the two relevant methods, should be something like:

    void start()
    {
        boost::asio::async_write(socket_, boost::asio::buffer(message_),
            boost::bind(&tcp_connection::handle_write, shared_from_this(),
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred));
    }

    void handle_write(const boost::system::error_code& error,
                      size_t bytes_transferred)
    {
        if (error) {
            if (socket_.is_open()) {
                std::cout<<"Error while sending data asynchronously"<<std::endl;
                socket_.close();
            }
        }
        if (socket_.is_open()) {
            boost::asio::async_write(socket_, boost::asio::buffer(message_),
                boost::bind(&tcp_connection::handle_write, shared_from_this(),
                            boost::asio::placeholders::error,
                            boost::asio::placeholders::bytes_transferred));
        }

    }    

That is, the connection thread only "ignite" an event driven loop, that is run op part of the ioservice's thread: as soon as a write is performed, the callback is invoked in order to post the next one.

like image 76
Sigi Avatar answered Oct 19 '22 17:10

Sigi