无法使用 boost asio TCP 套接字解释带宽性能差
Can't explain poor bandwidth performance using boost asio TCP sockets
下面是我写的一个简单的TCP服务器和匹配客户端开始练习boost的asio库的例子,Example TCP Client/Server。
- 客户端简单地连接并尽可能快地从内存缓冲区发送数据。
- 服务器仅侦听消息并打印从完整消息中获得的字节数。
仅此而已 - 仅此而已。几个线程上的 Booth 示例 运行,大部分使用默认设置,没有随意放置的 sleep 可能会把事情搞砸......它们真的很容易理解,除了直接调用以隔离问题为目标。
问题是,客户端的输出如下:
Mbytes/sec:51.648908,Gbytes/sec:0.051649,Mbits/sec:413.191267,Gbits/sec:0.413191
备注:
- 我现在运行正在关闭笔记本电脑的电池电源。如果我将它插入电源插孔,它会跳到 ~0.7 Gbits/sec.
- 我试过将 2048 字节的小消息发送到当前的 8 MB 消息。
- 我试过启用和禁用 nagle 算法。
- 我试过调整发送和接收 OS 缓冲区的大小。
- 所有这些都是 运行通过环回,
127.0.0.1
。
- 通过 Wireshark 监控环回显示相同的低带宽使用率。
促使我写这个问题的实际点是这一点。 iperf tool 能够通过本地主机使用 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 boost asio performance" 的搜索都倾向于以禁用 nagle 或调整 OS 套接字缓冲区的建议结束。
如果有人能指出正确的方向来理解为什么我使用 boost asio 获得如此低的带宽性能,我将不胜感激!
首先让我指出你做这种测试的错误。
- 手动设置 TCP 缓冲区大小
最好留给 TCP 算法找出最佳大小。这通常是在 TCP slow-start
阶段确定的,在此阶段 TCP 算法最终根据拥塞情况决定最佳可能 window-size
。由于我们使用的是本地主机,而且也是点对点连接,网络组件之间没有任何连接,因此拥塞将接近于零。
- 启用 Nagles 算法
这实际上并不需要,因为您发送的不是短长度的帧数据包。通常在那时打开 Nagles 会给你带来一些好处latency
(对于吞吐量我不太确定是否会有任何改进)。
- 服务器端不需要的处理
我看到您正在遍历接收到的缓冲区并进行某种无意义的检查。 iperf
肯定不会那样做。我已经注释掉了那部分代码。
- 接收应用程序缓冲区大小
我不知道,但出于某种原因,您选择每次接收只读取 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 英特尔酷睿 i5 处理器 - 8GB DDR3 内存
.
下面是我写的一个简单的TCP服务器和匹配客户端开始练习boost的asio库的例子,Example TCP Client/Server。
- 客户端简单地连接并尽可能快地从内存缓冲区发送数据。
- 服务器仅侦听消息并打印从完整消息中获得的字节数。
仅此而已 - 仅此而已。几个线程上的 Booth 示例 运行,大部分使用默认设置,没有随意放置的 sleep 可能会把事情搞砸......它们真的很容易理解,除了直接调用以隔离问题为目标。
问题是,客户端的输出如下:
Mbytes/sec:51.648908,Gbytes/sec:0.051649,Mbits/sec:413.191267,Gbits/sec:0.413191
备注:
- 我现在运行正在关闭笔记本电脑的电池电源。如果我将它插入电源插孔,它会跳到 ~0.7 Gbits/sec.
- 我试过将 2048 字节的小消息发送到当前的 8 MB 消息。
- 我试过启用和禁用 nagle 算法。
- 我试过调整发送和接收 OS 缓冲区的大小。
- 所有这些都是 运行通过环回,
127.0.0.1
。 - 通过 Wireshark 监控环回显示相同的低带宽使用率。
促使我写这个问题的实际点是这一点。 iperf tool 能够通过本地主机使用 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 boost asio performance" 的搜索都倾向于以禁用 nagle 或调整 OS 套接字缓冲区的建议结束。
如果有人能指出正确的方向来理解为什么我使用 boost asio 获得如此低的带宽性能,我将不胜感激!
首先让我指出你做这种测试的错误。
- 手动设置 TCP 缓冲区大小
最好留给 TCP 算法找出最佳大小。这通常是在 TCP slow-start
阶段确定的,在此阶段 TCP 算法最终根据拥塞情况决定最佳可能 window-size
。由于我们使用的是本地主机,而且也是点对点连接,网络组件之间没有任何连接,因此拥塞将接近于零。
- 启用 Nagles 算法
这实际上并不需要,因为您发送的不是短长度的帧数据包。通常在那时打开 Nagles 会给你带来一些好处latency
(对于吞吐量我不太确定是否会有任何改进)。
- 服务器端不需要的处理
我看到您正在遍历接收到的缓冲区并进行某种无意义的检查。 iperf
肯定不会那样做。我已经注释掉了那部分代码。
- 接收应用程序缓冲区大小
我不知道,但出于某种原因,您选择每次接收只读取 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 英特尔酷睿 i5 处理器 - 8GB DDR3 内存 .