我用C ++开发了一个小型的HTTP服务器,使用boost :: asio,现在我正在用多个客户端进行负载testing,而且我一直无法接近饱和CPU。 我正在testing一个Amazon EC2实例,使用一个cpu的50%,另一个cpu的20%,剩下的两个闲置(根据htop)。
细节:
那么,我应该看看如何改善这个结果呢? 鉴于CPU大多是空闲的,我想利用这个额外的容量来获得更高的吞吐量,比如说800个请求/秒或者其他任何东西。
我有过的想法:
boost :: asio不像你所希望的那样是线程友好的 – 在boost / asio / detail / epoll_reactor.hpp中epoll代码有一个很大的锁定,意味着一次只有一个线程可以调用内核的epoll系统调用。 而对于非常小的请求,这会造成所有差异(意味着您只能看到大致单线程的性能)。
请注意,这是boost :: asio如何使用Linux内核工具的限制,而不一定是Linux内核本身。 当使用边缘触发事件时,epoll系统调用确实支持多线程,但是正确的(没有过多的锁定)可能会非常棘手。
顺便说一下,我已经在这方面做了一些工作(将一个完全多线程的边缘触发的epoll事件循环与用户预定的线程/光纤结合起来),并在nginetd项目下提供了一些代码。
当您使用EC2时,所有投注都关闭。
尝试使用真正的硬件,然后你可能会看到发生了什么事情。 试图在虚拟机中进行性能测试基本上是不可能的。
我还没有计算出EC2是有用的,如果有人发现,请让我知道。
对于这种简单的异步请求,230个请求/秒似乎非常低。 因此,使用多线程可能是不成熟的优化 – 让它正常工作,并在单线程中调整,看看你是否仍然需要它们。 只是摆脱不需要的锁定可能会使速度加快。
本文在2003年左右的Web服务器风格性能的I / O策略方面有一些细节和讨论。任何人都有更近的东西?
从您对网络使用的评论中,
你似乎没有太多的网络运动。
3 + 2.5 MiB/sec
是在50Mbps
球场附近(与你的1Gbps端口相比)。
我会说你有以下两个问题之一,
看看cmeerw
的笔记和你的CPU利用率数字
(空转50% + 20% + 0% + 0%
)
这似乎是您的服务器实现中的一个限制。
我第二个cmeerw
的答案(+1)。
ASIO对于中小型的任务来说是不错的,但是它并不擅长利用底层系统的力量。 无论是原始套接字调用,甚至Windows上的IOCP,但如果你有经验,你总是会比ASIO更好。 无论哪种方式,所有这些方法都有很多开销,而ASIO则更多。
什么是值得的。 在我的自定义HTTP上使用原始套接字调用可以每秒用4个核心I7提供800K动态请求。 这是RAM的服务,这是你需要达到的性能水平。 在这样的性能水平上,网络驱动程序和操作系统消耗了大约40%的CPU。 使用ASIO我可以得到每秒50到100K的请求,其性能是相当可变的,并且大多数绑定在我的应用程序中。 @cmeerw的帖子主要解释了为什么。
一种提高性能的方法是通过实现一个UDP代理。 拦截HTTP请求,然后通过UDP将它们路由到您的后端UDP-HTTP服务器,可以绕过操作系统堆栈中的大量TCP开销。 你也可以通过UDP自己管理前端,这对你自己来说不是太难。 HTTP-UDP代理的一个优点是,它允许你在不修改的情况下使用任何好的前端,你可以随意的交换它们,而不会有任何影响。 你只需要更多的服务器来实现它。 对我的例子进行的这一修改将操作系统CPU使用率降低到了10%,这使得我的请求每秒增加到单个后端上的超过一百万。 和FWIW你应该总是有一个前端后端设置任何表演站点,因为前端可以缓存数据,而不会减慢更重要的动态请求后端。
未来似乎正在编写自己的驱动程序,实现自己的网络堆栈,以便尽可能接近请求并在那里实现自己的协议。 这可能不是大多数程序员想要听到的,因为它比较复杂。 在我的情况下,我将能够使用40%以上的CPU,并移动到每秒超过100万的动态请求。 UDP代理方法可以让你接近最佳的性能,而不需要这样做,但是你将需要更多的服务器 – 尽管如果你每秒要做这么多的请求,你通常需要多个网卡和多个前端来处理带宽,在那里一对轻量级的UDP代理并不是什么大不了的事情。
希望这对你有一些帮助。
io_service有多少个实例? Boost asio有一个例子 ,为每个CPU创建一个io_service并以RoundRobin的方式使用它们。
您仍然可以创建四个线程并为每个CPU分配一个线程,但是每个线程都可以轮询自己的io_service。