【问题标题】:An optimal number of worker threads in an async IO TCP server异步 IO TCP 服务器中的最佳工作线程数
【发布时间】:2017-02-06 15:20:46
【问题描述】:

我们已使用boost::asio 将每个连接通信模型的线程迁移到基于异步 IO 的 TCP 服务器。这种变化的原因是旧模型的扩展性不够好。我们平均有大约 2000 个永久连接,并且有每月保持增长的趋势。

我的问题是,将轮询 io_service 队列以获取完成处理程序的理想工作线程数是多少 - 虚拟 cpu 内核的数量?

选择较小的数字会导致服务器消耗不够快,无法应对客户端发送消息的速率的情况。

在这种情况下动态添加工作线程有意义吗?

更新: 可能这是我的实现,但我发现 boost asio 文档中的这个语句的一部分令人困惑:

诸如每个连接线程(仅同步方法需要)等实施策略可能会降低系统性能 性能,由于增加了上下文切换,同步和 CPU之间的数据移动。使用异步操作是可能的 通过最小化的数量来避免上下文切换的成本 操作系统线程——通常是有限的资源——并且只有 激活具有事件的逻辑控制线程 过程。

就好像你有 X 线程在有 X 核的机器上泵送完成事件 - 1)你不能保证每个线程都有一个专用的 cpu 和 2)如果我的连接是持久的,我没有任何保证执行 async_read 的线程与执行完成处理程序的线程相同。

void Connection::read {
    boost::asio::async_read(socket, boost::asio::buffer(buffer, 18),
                            boost::bind(&Connection::handleRead, shared_from_this(),
                            boost::asio::placeholders::error,
                            boost::asio::placeholders::bytes_transferred));
}

void Connection::handleRead(const boost::system::error_code &error,
                                               std::size_t bytes_transferred) {
    // processing of the bytes
    ...
    // keep processing on this socket
    read();
}

【问题讨论】:

  • 这显然是不可能回答的,除非说“这取决于”。我想说的是,理想的设计应该是一个,每个安装的 NIC 不超过两个内核线程,直到 NIC 带宽饱和。如果你超过了,那么你需要重构你的软件以不那么低效。阅读 nginx 如何实现 10 Gbps NIC 可扩展性,或聘请 ASIO 顾问专家为您提供建议。
  • 人们(不一定是你)往往会大大低估单线程的威力。单个线程可以做的工作量是巨大的。您需要平衡的问题是 1) 线程过多导致的上下文切换导致延迟和吞吐量问题,2) 泵送完成事件的线程太少导致高延迟和吞吐量低,以及 3) 线程过多导致工作集大小超过必要的大小堆栈导致 CPU 缓存性能不佳。所有这些对于您的工作量的平衡是最佳点,找到它的唯一方法是进行实验和测量。
  • 我想对此进行更多跟进。我们在异步 IO 方法中看到的是,延迟问题已按预期消失,但 ctxt 开关/秒的绝对数量大约是我们过去在每个连接模型中的线程中的 10 倍。换句话说,与旧模型中的约 1k 个工作线程相比,24 个 IO 线程泵送完成事件产生的上下文切换要多得多。您能分享一下您对此的看法吗?
  • @ladaManiak - 如果您的旧模型基于轮询,那么它可能不会像异步模型那样产生处理器,从而导致更多的上下文切换。我觉得您减少的延迟与此处上下文切换的增加直接相关 - 减少延迟通常会增加 CPU 开销。
  • @hoodaticus 我试图详细说明并编辑了我最初的问题。

标签: asynchronous io boost-asio


【解决方案1】:

在完美的非阻塞 I/O、完全适合 L1 缓存的工作集以及物理系统中没有其他进程的理想情况下,每个线程将使用处理器内核的全部资源。在这种情况下,理想的线程数是每个逻辑核心一个。

如果您的 I/O 的任何部分发生阻塞,那么添加比内核数量更多的线程是有意义的,这样没有内核处于空闲状态。如果一半的线程时间被阻塞,那么每个核心应该有近 2 个线程。如果 75% 的线程时间被阻塞,那么每个内核应该有 3 或 4 个,依此类推。为此目的,上下文切换开销算作阻塞。

我注意到,当 Microsoft 不得不对此进行盲目猜测时,他们倾向于每个内核使用两个或四个线程。根据您做出此决定的预算,我会选择 2 个或 4 个,或者从每个核心一个线程开始,然后逐步增加,测量吞吐量(服务的请求数/秒)和延迟(最小、最大和平均响应时间)直到我到达最佳位置。

只有在处理完全不同的程序时,动态调整此值才有意义。对于可预测的工作负载,您的硬件有一个最佳点,即使要完成的工作量增加,也不会发生太大变化。如果您正在制作通用 Web 服务器,则可能需要进行动态调整。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-06
    • 2021-05-09
    • 2015-08-06
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    相关资源
    最近更新 更多