Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)

  • 性能对比原则:采用对方的性能测试方案,用muduo实现功能相同 或类似的程序,然后放到相同的软硬件环境中对比。
  • 注意这里的测试只是简单地比较了平均值;其实在严肃的性能对比 中至少还应该考虑分布和百分位数(percentile)的值(http://zedshaw.com/essays/programmer_stats.html、http://www.percona.com/files/presentations/VELOCITY2012-Beyond-the-Numbers.pdf)。限于篇幅, 此处从略

一、muduo与Boost.Asio、libevent2的吞吐量对比

  • 陈硕先生在编写muduo的时候并没有以高并发、高吞吐为主要目标。但出乎意料,ping pong测试表明,muduo的吞吐量比Boost.Asio高15% 以上;比libevent2高18%以上,个别情况甚至达到70%

测试对象

  • boost 1.40中的asio 1.4.3
  • asio 1.4.5:http://think-async.com/Asio/Download
  • libevent 2.0.6-rc:http://monkey.org/~provos/libevent-2.0.6-rc.tar.gz
  • muduo 0.1.1

测试代码

  • asio的测试代码取自http://asio.cvs.sourceforge.net/viewvc/asio/asio/src/tests/performance,未做更改
  • 陈硕先生编写了libevent2的ping pong测试代码,路径是https://github.com/chenshuo/recipes/tree/master/pingpong/libevent。由于这个测试代码没有使用多线程,所以只 对比muduo和libevent2在单线程下的性能
  • muduo的测试代码位于https://github.com/dongyusheng/csdn-code/tree/master/muduo/examples/pingpong,代码如gist(http://gist.github.com/564985)所示
  • muduo和asio的优化编译参数均为-O2 -finline-limit=1000

测试环境

  • 硬件:DELL 490工作站,双路Intel四核Xeon E5320 CPU,共8核, 主频1.86GHz,内存16GiB。
  • 软件:操作系统为Ubuntu Linux Server 10.04.1 LTS x86_64,编译器 是g++ 4.4.3。

测试方法

  • 依据asio性能测试18的办法,用ping pong协议来测试muduo、asio、 libevent2在单机上的吞吐量
  • 简单地说,ping pong协议是客户端和服务器都实现echo协议。当 TCP连接建立时,客户端向服务器发送一些数据,服务器会echo回这些 数据,然后客户端再echo回服务器。这些数据就会像乒乓球一样在客户 端和服务器之间来回传送,直到有一方断开连接为止。这是用来测试吞 吐量的常用办法。注意数据是无格式的,双方都是收到多少数据就反射 回去多少数据,并不拆包,这与后面的ZeroMQ延迟测试不同
  • 主要做了两项测试:
    • 单线程测试。客户端与服务器运行在同一台机器,均为单线程, 测试并发连接数为1/10/100/1000/10000时的吞吐量
    • ·多线程测试。并发连接数为100或1000,服务器和客户端的线程数 同时设为1/2/3/4。(由于我家里只有一台8核机器,而且服务器和客户 端运行在同一台机器上,线程数大于4没有意义。)
  • 在所有测试中,ping pong消息的大小均为16KiB。测试用的shell脚本可从http://gist.github.com/564985下载
  • 在同一台机器测试吞吐量的原因如下:
    • 现在的CPU很快,即便是单线程单TCP连接也能把千兆以太网的带 宽跑满。如果用两台机器,所有的吞吐量测试结果都将是110MiB/s,失 去了对比的意义。(用Python也能跑出同样的吞吐量,或许可以对比哪 个库占的CPU少。)
    • 在同一台机器上测试,可以在CPU资源相同的情况下,单纯对比网 络库的效率。也就是说在单线程下,服务端和客户端各占满1个CPU, 比较哪个库的吞吐量高。

测试结果

  • 单线程测试的结果(如下图所示),数字越大越好。

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第1张图片

  • 以上结果让人大跌眼镜,muduo居然比libevent2快70%!跟踪 libevent2的源代码发现,它每次最多从socket读取4096字节的数据(证据在buffer.c的evbuffer_ read()函数),怪不得吞吐量比muduo小很多。 因为在这一测试中,muduo每次读取16384字节,系统调用的性价比较 高
  • 为了公平起见,我再测了一次,这回两个库都发送4096字节的消息(如下图所示):

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第2张图片

  • 测试结果表明muduo的吞吐量平均比libevent2高18%以上
  • 多线程测试的结果(如下图所示),数字越大越好

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第3张图片

  • 试结果表明muduo的吞吐量平均比asio高15%以上

讨论

  • muduo出乎意料地比asio性能优越,我想主要得益于其简单的设计 和简洁的代码。asio在多线程测试中表现不佳,我猜测其主要原因是测 试代码只使用了一个io_service,如果改用“io_service per CPU”的话,其 性能应该有所提高。我对asio的了解程度仅限于能读懂其代码,希望能 有asio高手编写“io_service per CPU”的ping pong测试,以便与muduo做一 个公平的比较
  • 由于libevent2每次最多从网络读取4096字节,这大大限制了它的吞 吐量。
  • ping pong测试很容易实现,欢迎其他网络库(ACE、POCO、 libevent等)也能加入到对比中来,期待这些库的高手出马。

二、击鼓传花:对比muduo与libevent2的事件处理效率

  • 前面我们比较了muduo和libevent2的吞吐量,得到的结论是muduo 比libevent2快18%.有人会说,libevent2并不是为高吞吐量的应用场景而 设计的,这样的比较不公平,胜之不武。为了公平起见,这回我们用 libevent2自带的性能测试程序(击鼓传花)来对比muduo和libevent2在 高并发情况下的IO事件处理效率。
  • 测试用的软硬件环境与前一小节相同,另外我还在自己的DELL E6400笔记本电脑上运行了测试,结果也附在后面。
  • 测试的场景是:有1000个人围成一圈,玩击鼓传花的游戏,一开始 第1个人手里有花,他把花传给右手边的人,那个人再继续把花传给右 手边的人,当花转手100次之后游戏停止,记录从开始到结束的时间
  • 用程序表达是,有1000个网络连接(socketpair()或pipe()),数据 在这些连接中顺次传递,一开始往第1个连接里写1个字节,然后从这个 连接的另一头读出这1个字节,再写入第2个连接,然后读出来继续写到 第3个连接,直到一共写了100次之后程序停止,记录所用的时间
  • 以上是只有一个活动连接的场景,我们实际测试的是100个或1000 个活动连接(即100朵花或1000朵花,均匀分散在人群手中),而连接 总数(即并发数)从100~100000(10万)。注意每个连接是两个文件 描述符,为了运行测试,需要调高每个进程能打开的文件数,比如设为 256000。
  • libevent2的测试代码位于test/bench.c,我修复了2.0.6-rc版里的一个 小bug。修正后的代码见已经提交给libevent2作者,现在下载的最新版本是正确的
  • muduo的测试代码为https://github.com/dongyusheng/csdn-code/blob/master/muduo/examples/pingpong/bench.cc

测试结果

  • 第一轮,分别用100个活动连接和1000个活动连接,无超时,读写 100次,测试一次游戏的总时间(包含初始化)和事件处理的时间(不 包含注册event watcher)随连接数(并发数)变化的情况。具体解释见 libev的性能测试文档(http://libev.schmorp.de/bench.html),不同之处在于我们不比较timer event的性能, 只比较IO event的性能。对每个并发数,程序循环25次,刨去第一次的 热身数据,后24次算平均值。测试用的脚本(https://github.com/chenshuo/recipes/blob/master/pingpong/libevent/run_bench.sh)是libev的作者Marc Lehmann写的,我略做改用,用于测试muduo和libevent2。
  • 第一轮的结果(见下图),请先只看“+”线(实线)和“×”线(粗 虚线)。“×”线是libevent2用的时间,“+”线是muduo用的时间。数字越 小越好。注意这个图的横坐标是对数的,每一个数量级的取值点为1, 2,3,4,5,6,7.5,10

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第4张图片

  • 从两条线的对比可以看出:
    • 1.libevent2在初始化event watcher方面比muduo快20%(左边的两 个图)
    • 2.在事件处理方面(右边的两个图)
      • a.在100个活动连接的情况下, 当总连接数(并发数)小于1000或大于30000时,二者性能差不 多; 当总连接数大于1000或小于30000时,libevent2明显领先
      • b.在1000个活动连接的情况下, 当并发数小于10000时,libevent2和muduo得分接近; 当并发数大于10000时,muduo明显占优
  • 这里有两个问题值得探讨:
    • 1.为什么muduo花在初始化上的时间比较多?
    • 2.为什么在一些情况下它比libevent2慢很多?

再次测试

  • 我仔细分析了其中的原因,并参考了libev的作者Marc Lehmann的观点(http://list.schmorp.de/pipermail/libev/2010q2/001041.html),结论是:在第一轮初始化时,libevent2和muduo都是用epoll_ctl(fd, EPOLL_CTL_ ADD, ...)来添加文件描述符的event watcher。不同之处在 于,在后面24轮中,muduo使用了epoll_ctl(fd, EPOLL_CTL_MOD, ...)来 更新已有的event watcher;然而libevent2继续调用epoll_ctl(fd, EPOLL_CTL_ADD, ...)来重复添加fd,并忽略返回的错误码 EEXIST(File exists)。在这种重复添加的情况下,EPOLL_CTL_ADD 将会快速地返回错误,而EPOLL_CTL_MOD会做更多的工作,花的时 间也更长。于是libevent2捡了个便宜。
  • 为了验证这个结论,我改动了muduo,让它每次都用 EPOLL_CTL_ADD方式初始化和更新event watcher,并忽略返回的错 误
  • 第二轮测试结果见上图的细虚线,可见改动之后的muduo的初始化 性能比libevent2更好,事件处理的耗时也有所降低(我推测是kernel内 部的原因)。
  • 这个改动只是为了验证想法,我并没有把它放到muduo最终的代码中去,这或许可以留作日后优化的余地。(具体的改动是https://github.com/dongyusheng/csdn-code/blob/master/muduo/muduo/net/poller/EPollPoller.cc的第138和173行,读者可自行验证
  • 同样的测试在双核笔记本电脑上运行了一次,结果如下图所示(我的笔记本电脑的CPU主频是2.4GHz,高于台式机的1.86GHz,所以用时较少)

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第5张图片

  • 结论:在事件处理效率方面,muduo与libevent2总体比较接近,各 擅胜场。在并发量特别大的情况下(大于10000),muduo略微占优

三、muduo与Nginx的吞吐量对比

  • 本节简单对比了Nginx 1.0.12和muduo 0.3.1内置的简陋HTTP服务器 的长连接性能。其中muduo的HTTP实现和测试代码位于https://github.com/dongyusheng/csdn-code/tree/master/muduo/muduo/net/http

测试环境

  • 服务端,运行HTTP server,8核DELL 490工作站,Xeon E5320 CPU。
  • 客户端,运行ab(http://httpd.apache.org/docs/2.4/programs/ab.html)和weighttp(http://redmine.lighttpd.net/projects/weighttp/wiki),4核i5-2500 CPU
  • 网络:普通家用千兆网

测试方法

  • 为了公平起见,Nginx和muduo都没有访问文件,而是 直接返回内存中的数据。毕竟我们想比较的是程序的网络性能,而不是 机器的磁盘性能。另外,这里客户机的性能优于服务机,因为我们要给 服务端HTTP server施压,试图使其饱和,而不是测试HTTP client的性能
  • muduo HTTP测试服务器的主要代码:
// 详细代码参阅: https://github.com/dongyusheng/csdn-code/blob/master/muduo/muduo/net/http/tests/HttpServer_test.cc
void onRequest(const HttpRequest& req, HttpResponse* resp)
{
  //...
  if (req.path() == "/")
  {
    //...
  }
  else if (req.path() == "/favicon.ico")
  {
    resp->setStatusCode(HttpResponse::k200Ok);
    resp->setStatusMessage("OK");
    resp->setContentType("image/png");
    resp->setBody(string(favicon, sizeof favicon));
  }
  else if (req.path() == "/hello")
  {
    resp->setStatusCode(HttpResponse::k200Ok);
    resp->setStatusMessage("OK");
    resp->setContentType("text/plain");
    resp->addHeader("Server", "Muduo");
    resp->setBody("hello, world!\n");
  }
  else
  {
    resp->setStatusCode(HttpResponse::k404NotFound);
    resp->setStatusMessage("Not Found");
    resp->setCloseConnection(true);
  }
}

int main(int argc, char* argv[])
{
  int numThreads = 0;
  if (argc > 1)
  {
    benchmark = true;
    Logger::setLogLevel(Logger::WARN);
    numThreads = atoi(argv[1]);
  }
  EventLoop loop;
  HttpServer server(&loop, InetAddress(8000), "dummy");
  server.setHttpCallback(onRequest);
  server.setThreadNum(numThreads);
  server.start();
  loop.loop();
}
  • Nginx使用了章亦春的HTTP echo模块(http://wiki.nginx.org/HttpEchoModule,配置文件:http://gist.github.com/1967026)来实现直接返回数据。配置文件如下:

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第6张图片

  • 客户端运行以下命令来获取/hello的内容,服务端返回字符串"hello, world!"

  • 先测试单线程的性能(见下图),横轴是并发连接数,纵轴为每 秒完成的HTTP请求响应数目,下同。在测试期间,ab的CPU使用率低 于70%,客户端游刃有余

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第7张图片

  • 再对比muduo 4线程和Nginx 4工作进程的性能(见下图)。当连接 数大于20时,top(1)显示ab的CPU使用率达到85%,已经饱和,因此换 用weighttp(双线程)来完成其余测试

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第8张图片

  • CPU使用率对比(百分比是top(1)显示的数值):
    • 10000并发连接,4 workers/threads,muduo是4×83%,Nginx是 4×75%
    • 1000并发连接,4 workers/threads,muduo是4×85%,Nginx是 4×78%
  • 初看起来Nginx的CPU使用率略低,但是实际上二者都已经把CPU 资源耗尽了。与CPU benchmark不同,涉及IO的benchmark在满负载下的 CPU使用率不会达到100%,因为内核要占用一部分时间处理IO。这里 的数值差异说明muduo和Nginx在满负荷的情况下,用户态和内核态的 比重略有区别
  • 测试结果显示muduo多数情况下略快,Nginx和muduo在合适的条件 下qps(每秒请求数)都能超过10万。值得说明的是,muduo没有实现完 整的HTTP服务器,而只是实现了满足最基本要求的HTTP协议,因此这 个测试结果并不是说明muduo比Nginx更适合用做httpd,而是说明muduo 在性能方面没有犯低级错误

四、muduo与ZeroMQ的延迟对比

  • 本节我们用ZeroMQ自带的延迟和吞吐量测试(http://wiki.zeromq.org/results:perf-howto)与muduo做一对比, muduo代码位于https://github.com/dongyusheng/csdn-code/tree/master/muduo/examples/zeromq。测试的内容很简单,可以认为是 §6.5.1 ping pong测试的翻版,不同之处在于这里的消息的长度是固定的,收到完整的消息再echo回发送方,如此往复
  • 测试结果如下图所示,横轴为消息的长度,纵轴为单程延迟(微秒)。可见在消息长度小于16KiB时,muduo的延迟稳定地低于ZeroMQ。

Linux(muduo网络库):20---muduo简介之(muduo性能测评:吞吐量、事件处理效率、延迟)_第9张图片

你可能感兴趣的:(Linux(muduo网络库),muduo性能测评,吞吐量,事件处理效率,延迟)