【问题标题】:Boost ASIO async_write_some is really slowBoost ASIO async_write_some 真的很慢
【发布时间】:2016-05-21 01:56:07
【问题描述】:

我终于找到了我的服务器的瓶颈,原来是async_writeasync_write_some也是如此。

下面是基准代码:

struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);

//boost::asio::async_write(mMainData.mSocket, boost::asio::buffer(pSendBuff->pBuffer, pSendBuff->dwUsedSize), mMainData.mStrand.wrap(boost::bind(&CServer::WriteHandler, pServer, this, pSendBuff, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
mMainData.mSocket.async_write_some(boost::asio::buffer(pSendBuff->pBuffer, pSendBuff->dwUsedSize), (boost::bind(&CServer::WriteHandler, pServer, this, pSendBuff, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));

clock_gettime(CLOCK_MONOTONIC, &end);

timespec temp;
if ((end.tv_nsec - start.tv_nsec) < 0)
{
    temp.tv_sec = end.tv_sec - start.tv_sec - 1;
    temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
}
else
{
    temp.tv_sec = end.tv_sec - start.tv_sec;
    temp.tv_nsec = end.tv_nsec - start.tv_nsec;
}

pLogger->WriteToFile("./Logs/Benchmark_SendPacketP_AsyncWrite.txt", "dwDiff: %.4f\r\n", (float)temp.tv_nsec / 1000000.0f);

还有输出:

-[2016.05.21 03:45:19] dwDiff: 0.0552ms
-[2016.05.21 03:45:19] dwDiff: 0.0404ms
-[2016.05.21 03:45:19] dwDiff: 0.0542ms
-[2016.05.21 03:45:20] dwDiff: 0.0576ms

这太慢了,因为它是一个游戏服务器,我需要在房间频道中广播数据包,一个频道中有 300 名玩家,想象一下它对我的玩家造成的网络延迟。

当然,这个测试是在服务器中只有我自己完成的。

是我的代码错误还是我在 ASIO 实现逻辑中遗漏了什么?

CXXFLAGS: -ggdb -ffunction-sections -Ofast -m64 -pthread -fpermissive -w -lboost_system -lboost_thread -Wall -fomit-frame-pointer
LDFLAGS: -Wl,-gc-sections -m64 -pthread -fpermissive -w -lboost_system -lboost_thread -lcurl

硬件是: Intel Xeon E3-1231v3(4核8线程) 64GB 内存 1GBPS 上行链路

我正在生成 8 个 ASIO 工作人员。

所以我用调试器进入了 async_write 并发现了这个:

template <typename ConstBufferSequence, typename Handler>
void async_send(base_implementation_type& impl,
  const ConstBufferSequence& buffers,
  socket_base::message_flags flags, Handler& handler)
{
bool is_continuation =
  boost_asio_handler_cont_helpers::is_continuation(handler);

// Allocate and construct an operation to wrap the handler.
typedef reactive_socket_send_op<ConstBufferSequence, Handler> op;
typename op::ptr p = { boost::asio::detail::addressof(handler),
  boost_asio_handler_alloc_helpers::allocate(
    sizeof(op), handler), 0 };
p.p = new (p.v) op(impl.socket_, buffers, flags, handler);

BOOST_ASIO_HANDLER_CREATION((p.p, "socket", &impl, "async_send"));

start_op(impl, reactor::write_op, p.p, is_continuation, true,
    ((impl.state_ & socket_ops::stream_oriented)
      && buffer_sequence_adapter<boost::asio::const_buffer,
        ConstBufferSequence>::all_empty(buffers)));
p.v = p.p = 0;
}

为什么 boost::asio 在一个应该是高性能的库中调用“new”? 有没有预先创建它试图分配的东西? 抱歉,我无法将内部结构描述为我使用 Microsoft Visual Studio 使用 VisualGDB 进行开发,并在 VMWare 中运行 GCC 4.8.5 工具集。

【问题讨论】:

  • 您需要展示更多代码。您处理此问题的方式更有可能存在问题,而不是booost::asio 的性能。 300 个客户是一个相对较小的数字。
  • 你想要哪个代码?我展示了瓶颈到底在哪里,恰好在计时检查代码(即 async_write)和文档之间,async_write 应该立即返回,因为它是异步操作,而不是阻塞操作,所以很明显它不是我的代码,除非你能告诉我不知道的事。
  • 我认为您错过了一个细节,因为我说过我需要广播频道状态,这意味着我需要经常向所有 300 名玩家发送频道更新数据包。
  • 作为一种解决方案,您可以将项目提交到队列中并使用 io_service:post() 来触发一系列写入操作。 post() 应该比 async_write_some() 返回更快
  • minimal reproducible example 或分析器将有助于归因性能。你能用example这样的东西复制同样的结果吗?另外,为什么不并行化广播呢?

标签: c++ networking boost boost-asio


【解决方案1】:

我知道这个答案有点晚了,但我会发布它以防有人觉得这很有用。

确实,在发布完成处理程序时会调用 new 。但是,官方文档解释了如何通过实现自定义内存管理来优化以避免相关的运行时开销。这是示例: custom memory management for completion handlers

【讨论】:

    【解决方案2】:

    如果没有分析器,试图确定哪条指令是瓶颈可能是对耐心的徒劳考验。创建一个最小示例可能有助于确定特定环境中问题的根源。例如,在既没有 I/O 也没有 io_service 争用的受控场景中,我观察到使用 native write()Asio's async_write() 时的 0.015ms~ 写入。

    试图解决的问题是以最小的延迟将相同的消息写入 300 个对等点。一种解决方案可能是将问题并行化:考虑使用并行运行的n 作业并将消息串行写入300/n 对等点,而不是让单个作业将消息串行写入 300 个对等点。粗略估计:

    • 如果连续执行 300 次写入,每次写入耗时 0.015 毫秒(在受控环境中使用原生 write() 时观察到的平均值),最终写入将在第一次写入后 4.485 毫秒开始。
    • 如果基于潜在并发限制(本例中为 8 个)批量写入 300 次,则将有 8 个作业并行运行,串行执行 38 次写入。如果每次写入耗时 0.0576ms(在实际系统上观察),则最终写入将在第一次写入后 2.13ms 开始。

    根据上述估计,通过并行化问题,写入 300 个对等点需要一半的时间,即使每个单独的 asyc_write 操作花费的时间比预期的要长。请记住,这些只是粗略估计,需要进行分析以确定理想的并发量,并确定潜在的瓶颈。

    【讨论】:

    • 非常感谢您的贡献。正如你所说,我也可以通过调用本机写入来获得相同的延迟,这可能是 Linux 特定的吗?如果是这种情况,Windows 可能会有不同的延迟?因为如果是,我将获得一个 dedi Windows 服务器并运行测试。
    • @NoobyLearner 我表达得很糟糕。在答案中,我链接到演示本机 write() 和 Asio 的 async_write() 占用 0.015ms~ 的示例。你能重现minimal reproducible example 中 0.05 毫秒的延迟吗?如果您想识别成为瓶颈的指令,请使用分析器。但是,我怀疑更改算法会产生更好的结果。
    • 目前,我已经在所有 ASIO 线程之间分配了 async_write。此外,我没有为每个播放器发送多个更新数据包,而是将所有数据包复制到一个缓冲区并在一个 async_write 调用中发送。但是,有时由于设计原因,我无法将所有数据包复制到一个数据包中,因此我将所有缓冲区插入“vector<:asio::const_buffer>”并将它们全部发送到一个 async_write 调用中。到目前为止,这已经提高了性能,但是我仍然希望尽量减少延迟。
    • Tanner,通过您的测试,您通过调用本机 write() 获得了 0.015 毫秒的延迟,是否可以对 linux sysctl 进行一些自定义配置以改善这种延迟?我在想,如果 ASIO 收到套接字已准备好写入的通知,那么 write 调用应该是超快的。
    猜你喜欢
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-01
    • 1970-01-01
    • 2017-11-10
    • 2010-12-28
    • 2013-01-25
    相关资源
    最近更新 更多