1. 程式人生 > >Boost.Asio效能測試

Boost.Asio效能測試

c++ boost::asio
connect=10000,active connect=100,req=148791,time=60,req/sec=2479.85,msec/req=40.343

erlang kernel-poll false
connect=10000,active connect=100,req=979803,time=60,req/sec=16330,msec/req=6.12356

node.js
connect=10000,active connect=100,req=1378370,time=60,req/sec=22972.8,msec/req=4.35543

c libevent
connect=10000,active connect=100,req=3719106,time=60,req/sec=61985.1,msec/req=1.61258

erlang kernel-poll true
connect=10000,active connect=100,req=6377574,time=60,req/sec=106293,msec/req=0.939882

看到這個資料,立馬嚇尿了. 見過黑C++,黑Boost的,沒見過黑這麼狠的.這資料簡直是要顛覆我的世界觀啊!!!

沒有辦法,為了查明真相,只能自己寫個程式測試一下. 原帖子裡面包含了一個asio的echo_server, 稍微看了一下,程式碼沒多大問題,就是delete this有點刺眼, 於是順便改了一下程式.

// echo_server.cpp
// g++ -o echo_server -O3 echo_server.cpp -lboost_system -lboost_thread
#include <cstdlib> #include <iostream> #include <boost/bind.hpp> #include <boost/asio.hpp> #include <boost/thread/thread.hpp> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> using boost::asio::ip::tcp; int total_conn = 0; class session : public
boost::enable_shared_from_this<session> { public: session(boost::asio::io_service& io_service) : socket_(io_service) { } tcp::socket& socket() { return socket_; } void start() { socket_.async_read_some(boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } private: void handle_read(const boost::system::error_code& error, size_t bytes_transferred) { if (!error) { boost::asio::async_write(socket_, boost::asio::buffer(data_, bytes_transferred), boost::bind(&session::handle_write, shared_from_this(), boost::asio::placeholders::error)); } } void handle_write(const boost::system::error_code& error) { if (!error) { socket_.async_read_some(boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } } tcp::socket socket_; enum { max_length = 1024 }; char data_[max_length]; }; class server { public: server(boost::asio::io_service& io_service, short port) : io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port)) { start_accept(); } private: void start_accept() { boost::shared_ptr<session> new_session(new session(io_service_)); acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, boost::asio::placeholders::error)); } void handle_accept(boost::shared_ptr<session> new_session, const boost::system::error_code& error) { start_accept(); if (!error) { std::cout << "total connect =" << ++total_conn <<std::endl; new_session->start(); } } boost::asio::io_service& io_service_; tcp::acceptor acceptor_; }; int main(int argc, char* argv[]) { try { if (argc < 2) { std::cerr << "Usage: async_tcp_echo_server <port>\n"; return 1; } boost::asio::io_service io_service; using namespace std; // For atoi. server s(io_service, atoi(argv[1])); int thread_num = 6; if (argc > 2) thread_num = atoi(argv[2]); boost::thread_group th_group; for (int i=0; i<thread_num; ++i) { th_group.add_thread(new boost::thread(boost::bind(&boost::asio::io_service::run, &io_service))); } th_group.join_all(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; }

然後把nodejs, erlang, libevent都拿來對比一下(nodejs還真是簡潔)

/// echo_server.js
var net = require("net");

var server = net.createServer(function(req) {
    req.on('data', function(data) {
        req.write(data);
    });
});
server.listen(8000);

下面是erlang的

-module(echo_server).
-export([start/0]).

start() ->
        {ok, Listen} = gen_tcp:listen(9000, [binary,
                                                %{packet, 4},
                                                {reuseaddr, true},
                                                {backlog, 2000},
                                                {active, true}]),
        spawn(fun() -> par_connect(Listen, 0) end).

par_connect(Listen, Count) ->
        {ok, Socket} = gen_tcp:accept(Listen),
        New = Count + 1,
        io:format("Accept succ ~p~n", [New]),
        spawn(fun() -> par_connect(Listen, New) end),
        loop(Socket).

loop(Socket) ->
    receive
        {tcp, Socket, Bin} ->
            gen_tcp:send(Socket, Bin),
            loop(Socket);
        {tcp_closed, Socket} ->
            io:format("Server socket closed~n")
    end.

下面是libevent版本

#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>  
#include <sys/socket.h>  
#include <sys/types.h>  
#include <sys/socket.h>
#include <event.h>  
#include <stdio.h>  
#include <time.h> 
#include <string.h>
#include <fcntl.h>

int buf_len = 8192;
int msg_len = 4096; 
int total = 0;

int setnonblock(int fd)
{       
	int flags;       
	flags = fcntl(fd, F_GETFL);       
	if (flags < 0)               
		return flags;       
	flags |= O_NONBLOCK;       
	if (fcntl(fd, F_SETFL, flags) < 0)               
		return -1;       

	return 0;
}

void connection_echo(int fd, short event, void *arg)
{
	struct event *ev = (struct event *)arg;
	event_add(ev, NULL);

	char buf[buf_len];
	int read_len = read(fd, buf, msg_len);
	write(fd, buf, read_len);
}

void connection_accept(int fd, short event, void *arg)   
{ 
    /* for debugging */ 
	//fprintf(stderr, "%s(): fd = %d, event = %d,	total = %d.\n", __func__, fd, event, ++total);  

    /* Accept a new connection. */ 
    struct sockaddr_in s_in;  
    socklen_t len = sizeof(s_in);  
    int ns = accept(fd, (struct sockaddr *) &s_in, &len);  
    if (ns < 0) {  
        perror("accept");  
        return;  
    }  

	setnonblock(ns);

    /* Install echo server. */ 
    struct event *ev = (struct event *)malloc(sizeof(struct event));  
    event_set(ev, ns, EV_READ, connection_echo, ev);  
    event_add(ev, NULL);  
} 

int main(void)  
{  
    /* Request socket. */ 
    int s = socket(PF_INET, SOCK_STREAM, 0);  
    if (s < 0) {  
        perror("socket");  
        exit(1);  
    }  

    /* bind() */ 
    struct sockaddr_in s_in;  
    memset(&s_in, 0, sizeof(s_in));  
    s_in.sin_family = AF_INET;  
    s_in.sin_port = htons(9000);  
    s_in.sin_addr.s_addr = INADDR_ANY;  
    if (bind(s, (struct sockaddr *) &s_in, sizeof(s_in)) < 0) {  
        perror("bind");  
        exit(1);  
    }  

    /* listen() */ 
    if (listen(s, 1000) < 0) {  
        perror("listen");  
        exit(1);  
    }  

    /* Initial libevent. */ 
    event_init();  

    /* Create event. */ 
    struct event ev;  
    event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev);  

    /* Add event. */ 
    event_add(&ev, NULL);  

    event_dispatch();  

    return 0;  
} 

最後是測試程式,用我們最愛的asio寫的, 發起10000的非同步連線, 連線成功後寫入"hello world",然後等待返回,返回後就斷開連線

// echo_client.cpp
// g++ -o echo_client -O3 echo_client.cpp -lboost_system -lboost_thread
#include <boost/asio.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>


class session 
	: public boost::enable_shared_from_this<session>
{
public:
	session(asio::io_service& io)
		: socket_(io)
	{ }

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

	void start()
	{
		asio::async_write(socket_, asio::buffer(output_buffer_, 12), boost::bind(&session::handle_write, shared_from_this(), _1, _2));
	}

	void handle_write(const boost::system::error_code& ec, std::size_t bytes_transfered)
	{
		if(!ec) 
		{
			asio::async_read(socket_, asio::buffer(input_buffer_, 12), boost::bind(&session::handle_read, shared_from_this(), _1, _2));
		} else {
			std::cerr << "write error:" << ec.message() << std:: endl;
		}
	}

	void handle_read(const boost::system::error_code& ec, std::size_t bytes_transfered)
	{
		if(ec)
		{
			std::cerr << "read error:" << ec.message() << std::endl;
		}
	}

private:
	tcp::socket socket_;
	char output_buffer_[12];
	char input_buffer_[12];
};


void handle_connect(boost::shared_ptr<session> session_ptr, const boost::system::error_code& ec)
{
	if(ec) 
	{
		std::cerr << "connect error:" << ec.message() << std::endl;
	} else {
		session_ptr->start();
	}
}


int main(int argc, char* argv[])
{
	asio::io_service io;
	tcp::resolver resolver(io);
	tcp::resolver::iterator endpoint = resolver.resolve(tcp::resolver::query("localhost", argv[1]));
	boost::shared_ptr<session> session_ptr;
	for(int i = 0; i < 10000; i++)
	{
		session_ptr.reset(new session(io));
		asio::async_connect(session_ptr->socket(), endpoint, boost::bind(handle_connect, session_ptr, _1));
	}
	io.run();
}

我首先測試了一下c++版本, 結果悲劇了,開2000個連線,居然要3s?

但是靠著堅定的asio/C++信仰, 仔細想了一想原因, 接著看了一下伺服器日誌,發現伺服器連線數大概在1000多. 差不多每次都是這麼多. 估計是ulimit的限制, 使用ulimit -n 99999放開伺服器和客戶端的限制. 果然,處理10000連線只需0.7秒.

c++測試結果:

localhost test # time ./echo_client 8000

real    0m0.798s
user    0m0.169s
sys     0m0.626s
localhost test # time ./echo_client 8000

real    0m0.843s
user    0m0.132s
sys     0m0.707s
localhost test # time ./echo_client 8000

real    0m0.762s
user    0m0.161s
sys     0m0.598s
localhost test # time ./echo_client 8000

real    0m0.774s
user    0m0.145s
sys     0m0.628s

但是測試過程中碰到了另一個問題,就是重複測試的時候,會在連線時出現 Cannot assign requested address 錯誤. 經過網上查詢資料, 是可用埠被用光了,系統還沒來得及回收.使用如下命令開啟埠tw_reuse

echo "1" > /proc/sys/net/ipv4/tcp_tw_reuse

這些測試就ok了,每次都在0.7秒左右.

接下來測試 nodejs

nodejs測試結果:

localhost test # time ./echo_client 8000

real    0m3.193s
user    0m0.140s
sys     0m0.484s
localhost test # time ./echo_client 8000

real    0m0.729s
user    0m0.144s
sys     0m0.494s
localhost test # time ./echo_client 8000

real    0m3.176s
user    0m0.141s
sys     0m0.489s
localhost test # time ./echo_client 8000

real    0m3.727s
user    0m0.136s
sys     0m0.489s

下面是libevent的結果, 效率挺不錯的, 比asio快了10%左右(有人想問,為什麼只有一次測試? 那是因為server掛掉了, 我不是很懂c, 不知道記憶體洩露出在哪裡)

localhost test # time ./echo_client 9000

real    0m0.643s
user    0m0.130s
sys     0m0.512s

最後是那位作者力挺的erlang, 
還好我看過幾天erlang,要不然我都不知道程式是怎麼執行的
erl +K true 啟用kernel-poll進入shell,c(echo_server). 編譯echo_server程式.echo_server:start(). 開啟伺服器

/// 前面還有好多好多reset
..... 
read error:Connection reset by peer
read error:Connection reset by peer

real    0m13.207s
user    0m0.168s
sys     0m0.418s

結果就是這個結果, 要是不信的話可以自己去試試.