【问题标题】:Can't explain poor bandwidth performance using boost asio TCP sockets无法解释使用 boost asio TCP 套接字的带宽性能差
【发布时间】:2016-11-27 16:57:07
【问题描述】:

以下是我编写的一个简单的 TCP 服务器和匹配客户端的示例,以开始练习 boost 的 asio 库,Example TCP Client/Server

  • 客户端只需连接并尽可能快地从内存缓冲区发送数据。
  • 服务器仅侦听消息并打印从完整消息中获得的字节数。

就是这样 - 仅此而已。 Booth 示例在几个线程上运行,大部分使用默认设置,没有随意放置的睡眠可能会导致事情失败......他们真的很容易理解,除了直接调用提升目标之外几乎没有什么隔离问题。

问题是,客户端的输出如下:

Mbytes/sec:51.648908,Gbytes/sec:0.051649,Mbits/sec:413.191267,Gbits/sec:0.413191

注意事项:

  • 我现在正在使用电池供电的笔记本电脑。如果我将它插入电源插孔,它会跳到 ~0.7 Gbits/sec。
  • 我尝试将 2048 字节的小消息发送到当前的 8 MB 消息。
  • 我尝试过启用和禁用 nagle 算法。
  • 我已尝试调整发送和接收操作系统缓冲区的大小。
  • 所有这些都通过环回运行,127.0.0.1
  • 通过 Wireshark 监控环回显示相同的低带宽使用情况。

促使我写这个问题的实际点是这个。 iperf tool 能够通过 localhost 使用 TCP 实现 33.0 Gbits/sec

$ iperf --client 127.0.0.1
------------------------------------------------------------
Client connecting to 127.0.0.1, TCP port 5001
TCP window size: 2.50 MByte (default)
------------------------------------------------------------
[  3] local 127.0.0.1 port 41952 connected with 127.0.0.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  38.4 GBytes  33.0 Gbits/sec

所有与“TCP 提升 asio 性能”相关的搜索往往都会以禁用 nagle 或调整操作系统套接字缓冲区的建议告终。

如果有人能指出正确的方向来理解为什么我使用 boost asio 获得如此低的带宽性能,我将不胜感激!

【问题讨论】:

  • 我通过注释掉所有套接字接收和发送缓冲区大小选项并将所需的大小调整推迟到 TCP 自动调整来将其提高到 3GBps。此外,我将服务器接收代码更改为不对缓冲区进行迭代,这是一种浪费。我在melpon.org/wandbox/permlink/UUlmqwH5tlD4Ah4f 更改的代码
  • 最好看看iperf在做什么。可能是使用一些零拷贝机制来发送?
  • 哦..我没有看到 iperf 中每秒 33G'bits'。然后通过我的更改,它是每秒 24Gbits :)。足够接近,但我相信可以做很多事情。再次重新运行它显示这次大约 30 Gbps。

标签: c++ networking tcp boost-asio


【解决方案1】:

首先让我指出你在这种测试中做错的事情。

  1. 手动设置 TCP 缓冲区大小

最好留给 TCP 算法来确定最佳大小。这通常在 TCP slow-start 阶段确定,在该阶段 TCP 算法最终根据拥塞情况决定最佳可能的 window-size。由于我们使用的是本地主机,而且网络组件之间也没有任何点对点连接,因此拥塞将接近于零。

  1. 启用 Nagles 算法

这实际上是不需要的,因为您没有发送短长度的成帧数据包。通常,当打开 Nagles 时,latency 会给您带来一些好处(对于吞吐量,我不太确定是否会有任何改进)。

  1. 服务器端不需要的处理

我看到您正在迭代接收到的缓冲区并进行某种无意义的检查。 iperf 肯定不会那样做。我已经注释掉了那段代码。

  1. 接收应用程序缓冲区大小

我不知道,但出于某种原因,您选择每次接收只读取 2048 字节。有什么特别的原因吗?我已将其更改回客户端正在写入的实际大小。您可能只是在服务器接收部分排队更多数据。

新服务器代码:

#include <thread>
#include <chrono>
#include <vector>
#include <signal.h>
#include <asio.hpp>
#include <system_error>

namespace
{
bool keepGoing = true;
void shutdown(int)
{
        keepGoing = false;
}

std::size_t bytesAccum = 0;
void justReceive(std::error_code ec, std::size_t bytesReceived,
    asio::ip::tcp::socket &socket, std::vector<unsigned char> &buffer)
{
        bytesAccum += bytesReceived;
/*
        auto end = buffer.begin() + bytesReceived;
        for (auto it = buffer.begin(); it != end; ++it)
        {
                if (*it == 'e')
                {
                        std::printf("server got: %lu\n", bytesAccum);
                        bytesAccum = 0;
                }
        }
*/
        socket.async_receive(
            asio::buffer(buffer),
            0,
            [&] (auto ec, auto bytes) {
              justReceive(ec, bytes, socket, buffer);
            });
}
}

int main(int, char **)
{
        signal(SIGINT, shutdown);

        asio::io_service io;
        asio::io_service::work work(io);

        std::thread t1([&]() { io.run(); });
        std::thread t2([&]() { io.run(); });
        std::thread t3([&]() { io.run(); });
        std::thread t4([&]() { io.run(); });

        asio::ip::tcp::acceptor acceptor(io,
            asio::ip::tcp::endpoint(
                asio::ip::address::from_string("127.0.0.1"), 1234));
        asio::ip::tcp::socket socket(io);

        // accept 1 client
        std::vector<unsigned char> buffer(131072, 0);
        acceptor.async_accept(socket, [&socket, &buffer](std::error_code ec)
        {
            // options
            //socket.set_option(asio::ip::tcp::no_delay(true)); 
            //socket.set_option(asio::socket_base::receive_buffer_size(8192  * 2));
            //socket.set_option(asio::socket_base::send_buffer_size(8192));

            socket.async_receive(
                asio::buffer(buffer),
                0,
                [&](auto ec, auto bytes) {
                  justReceive(ec, bytes, socket, buffer);
                });
        });

        while (keepGoing)
        {
                std::this_thread::sleep_for(std::chrono::seconds(1));
        }

        io.stop();

        t1.join();
        t2.join();
        t3.join();
        t4.join();

        std::printf("server: goodbye\n");
}

新客户端代码:

#include <thread>
#include <chrono>
#include <vector>
#include <signal.h>
#include <asio.hpp>
#include <system_error>

namespace
{
bool keepGoing = true;
void shutdown(int) { keepGoing = false; }
}

int main(int, char **)
{
        signal(SIGINT, shutdown);

        asio::io_service io;
        asio::io_service::work work(io);

        std::thread t1([&]() { io.run(); });
        std::thread t2([&]() { io.run(); });
        std::thread t3([&]() { io.run(); });
        std::thread t4([&]() { io.run(); });

        asio::ip::tcp::socket socket(io);
        auto endpoint = asio::ip::tcp::resolver(io).resolve({ 
            "127.0.0.1", "1234" });
        asio::connect(socket, endpoint);

        // options to test
        //socket.set_option(asio::ip::tcp::no_delay(true)); 
        //socket.set_option(asio::socket_base::receive_buffer_size(8192));
        //socket.set_option(asio::socket_base::send_buffer_size(8192 * 2));

        std::vector<unsigned char> buffer(131072, 0);
        buffer.back() = 'e';

        std::chrono::time_point<std::chrono::system_clock> last = 
            std::chrono::system_clock::now();

        std::chrono::duration<double> delta = std::chrono::seconds(0);

        std::size_t bytesSent = 0;

        while (keepGoing)
        {
                // blocks during send
                asio::write(socket, asio::buffer(buffer));
                //socket.send(asio::buffer(buffer));

                // accumulate bytes sent
                bytesSent += buffer.size();

                // accumulate time spent sending
                delta += std::chrono::system_clock::now() - last;
                last = std::chrono::system_clock::now();

                // print information periodically
                if (delta.count() >= 5.0) 
                {
                        std::printf("Mbytes/sec: %f, Gbytes/sec: %f, Mbits/sec: %f, Gbits/sec: %f\n",
                                    bytesSent / 1.0e6 / delta.count(),
                                    bytesSent / 1.0e9 / delta.count(),
                                    8 * bytesSent / 1.0e6 / delta.count(),
                                    8 * bytesSent / 1.0e9 / delta.count());

                        // reset accumulators
                        bytesSent = 0;
                        delta = std::chrono::seconds(0);
                }
        }

        io.stop();

        t1.join();
        t2.join();
        t3.join();
        t4.join();

        std::printf("client: goodbyte\n");
}

注意:我使用了独立版本的 asio,但 OP 报告的结果可以在我的机器上重现:

MacBook Pro Yosemite - 2.6 GHz Intel Core i5 处理器 - 8GB DDR3 RAM .

【讨论】:

  • 感谢您的观察!我在上传示例时混淆了我的文件,但我也尝试了默认的 boost::asio 设置(没有弄乱 nagle 或 OS 套接字缓冲区大小)。我没有尝试的是您的第 4 点。这就是我的例子中的瓶颈! Boost 只是在服务器端使用如此小的入站缓冲区做太多工作。放大它可以让我在 i5 8Gb RAM Linux 系统上达到 40 Gbits/sec。再次感谢您的帮助!
  • @Arunmu slow-start 相位是什么?
猜你喜欢
  • 2017-10-16
  • 2012-11-10
  • 1970-01-01
  • 2014-01-24
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 2013-10-08
  • 1970-01-01
相关资源
最近更新 更多