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

时间:2009-08-05 17:56:44

标签: c++ linux multithreading scalability boost-asio

我用C ++开发了一个迷你HTTP服务器,使用boost :: asio,现在我正在用多个客户端加载测试它,我一直无法接近使CPU饱和。我正在测试一个Amazon EC2实例,大约50%使用一个cpu,20%使用另一个cpu,其余两个闲置(根据htop)。

详细说明:

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

那么,我应该怎么看才能改善这个结果?鉴于CPU大部分处于空闲状态,我想利用这些额外的容量来获得更高的吞吐量,比如800请求/秒等等。

我的想法:

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

6 个答案:

答案 0 :(得分:44)

boost :: asio不像你希望的那样是线程友好的 - 在boost / asio / detail / epoll_reactor.hpp中围绕epoll代码有一个很大的锁定,这意味着只有一个线程可以调用内核的epoll系统调用一次。对于非常小的请求,这会产生重大影响(意味着您只会看到大致单线程的性能)。

请注意,这是对boost :: asio如何使用Linux内核工具的限制,而不一定是Linux内核本身。 epoll系统调用在使用边缘触发事件时确实支持多个线程,但正确(没有过多锁定)可能非常棘手。

顺便说一下,我一直在这方面做一些工作(将全多线程边缘触发的epoll事件循环与用户调度的线程/光纤相结合),并在nginetd项目下提供了一些代码。

答案 1 :(得分:10)

当您使用EC2时,所有投注均已关闭。

尝试使用真实硬件,然后你就可以看到发生了什么。尝试在VM中进行性能测试基本上是不可能的。

我还没有弄清楚EC2有用的东西,如果有人发现,请告诉我。

答案 2 :(得分:3)

根据您对网络利用率的评论,
你似乎没有太多的网络运动。

3 + 2.5 MiB/sec位于50Mbps球场周围(与您的1Gbps端口相比)。

我说你遇到以下两个问题之一,

  1. 工作量不足(客户请求率低)
    • 在服务器中阻止(受干扰的响应生成)
  2. 查看cmeerw的注释和CPU利用率数字
    (空转50% + 20% + 0% + 0%
    它似乎很可能是您服务器实施的限制 我第二次cmeerw的答案(+1)。

答案 3 :(得分:3)

对于这种简单的异步请求,230请求/秒似乎非常低。因此,使用多个线程可能是过早优化 - 使其正常工作并在单个线程中进行调整,并查看是否仍需要它们。摆脱不需要的锁定可能会让事情变得更快。

This article对于大约2003年的Web服务器式性能的I / O策略有一些细节和讨论。任何人都有更新的东西吗?

答案 4 :(得分: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代理并没有那么重要。

希望其中一些对您有用。

答案 5 :(得分:0)

你有多少个io_service实例? Boost asio有一个example,它为每个CPU创建一个io_service,并以RoundRobin的方式使用它们。

您仍然可以创建四个线程并为每个CPU分配一个线程,但每个线程都可以在自己的io_service上进行轮询。