C ++套接字服务器 – 无法饱和CPU

我用C ++开发了一个小型的HTTP服务器,使用boost :: asio,现在我正在用多个客户端进行负载testing,而且我一直无法接近饱和CPU。 我正在testing一个Amazon EC2实例,使用一个cpu的50%,另一个cpu的20%,剩下的两个闲置(根据htop)。

细节:

  • 服务器启动每个核心一个线程
  • 请求被接收,parsing,处理,并且响应被写出
  • 这些请求是针对从内存中读取的数据(只读用于此testing)
  • 我使用两台机器“加载”服务器,每台机器运行一个Java应用程序,运行25个线程,发送请求
  • 我看到大约230个请求/秒的吞吐量(这是应用程序请求,它由许多HTTP请求组成)

那么,我应该看看如何改善这个结果呢? 鉴于CPU大多是空闲的,我想利用这个额外的容量来获得更高的吞吐量,比如说800个请求/秒或者其他任何东西。

我有过的想法:

  • 请求是非常小的,经常在几个毫秒内完成,我可以修改客户端发送/撰写更大的请求(也许使用批处理)
  • 我可以修改HTTP服务器使用selectdevise模式,这是适当的吗?
  • 我可以做一些分析,试图了解瓶颈是什么

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端口相比)。

我会说你有以下两个问题之一,

  1. 工作负荷不足(客户请求率低)
    • 在服务器中阻塞(干扰响应生成)

看看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。