从标准输出读取的奇怪性能问题

Posted

技术标签:

【中文标题】从标准输出读取的奇怪性能问题【英文标题】:Strange performance issues reading from stdout 【发布时间】:2012-06-01 17:50:25 【问题描述】:

我正在编写一些将用于测试其他可执行文件的代码。为方便起见,我将我的代码称为测试器,将被测试的代码称为客户端。测试人员将生成客户端并向客户端的标准输入发送命令,并从客户端的标准输出接收结果。

我想先做一些性能测试,所以我写了一个非常简单的示例测试器和客户端。测试仪等待客户端将“READY”写入其标准输出,并作为响应将“GO”发送到客户端的标准输入。然后客户端将一些字节数写入标准输出,通过命令行标志配置,然后写入“\nREADY\n”,此时测试仪将再次写入“GO”。这会重复 10,000 次,然后我计算完成测试所需的时间和“吞吐量”,即 10,000 除以完成时间。

我运行上述测试,让客户端在发送“READY”之前发送 0、10、100、1000、10000 和 100000 字节的数据。对于每个字节大小,我重复测试 10 次并取平均值。在我的笔记本电脑上运行 Ubuntu VMWare 实例时,我获得了每秒大约 100k GO/READY 对的吞吐量。性能相当稳定,几乎不依赖于客户端发送给测试仪的二进制字节数。然后我在运行 CentOS 的速度非常快的 24 核服务器上重复了测试。对于 0 字节有效负载,我观察到每秒只有大约 55k GO/READY 对,并且随着客户端发送的字节数增加,性能明显下降。当客户端在“GO”和“READY”之间发送 100k 字节时,吞吐量仅为每秒大约 6k 次操作。

所以我有三个问题

    为什么相同的代码在更快的机器上运行会慢得多 为什么虚拟机的性能与负载大小无关,而快速服务器上的性能却在很大程度上取决于负载大小? 我能做些什么来加快服务器上的速度

一种可能的解释是我在快速服务器上重新编译了代码,并且它使用了不同版本的 C++ 库。 VMWare 机器运行 Ubuntu 11.10,快速服务器运行 CentOS 6。两者都是 64 位机器。

相关测试器代码如下:

ios_base::sync_with_stdio(false);
const int BUFFER_SIZE = 2 << 20;
char buffer[BUFFER_SIZE];
process_stdout->rdbuf()->pubsetbuf(buffer, BUFFER_SIZE);
Timer timer;
// Wait until the process is ready
string line;
line.reserve(2 << 20);
getline(*process_stdout, line);
CHECK(line == "READY");
timer.Start();
for (int i = 0; i < num_trials; ++i) 
  *process_stdin << "GO\n";
  process_stdin->flush();
  line = "";
  while (line != "READY") 
    getline(*process_stdout, line);
  

double elapsed = timer.Elapsed();
cout << "Done. Did " << num_trials << " iterations in "
     << elapsed << " seconds. Throughput: "
     << double(num_trials) / elapsed << " per second." << endl;

我还尝试了使用 read() 调用(来自 unistd.h)到 1MB 缓冲区并调用 memchr 以查找“\n”字符并查找 READY 的版本,但得到了相同的性能结果。

相关客户端代码如下:

// Create a vector of binary data. Some portion of the data will be sent 
// to stdout each time a "GO" is received before sending "READY"
vector<char> byte_source;
const int MAX_BYTES = 1 << 20;
for (int i = 0; i < MAX_BYTES; ++i) 
 byte_source.push_back(i % 256);


cout << "READY" << endl;
while (cin.good()) 
  string line;
  getline(cin, line);
  if (line == "GO") 
    // The value of response_bytes comes from a command line flag
    OutputData(response_bytes, byte_source);
    cout << "READY" << endl;
  


// write bytes worth of data from byte_source to stdout
void OutputData(unsigned int bytes,
                const vector<char>& byte_source) 
  if (bytes == 0) 
    return;
  
  cout.write(&byte_source[0], bytes);
  cout << "\n";

任何帮助将不胜感激!

【问题讨论】:

您是否在启用优化的情况下进行编译?如果不是,任何性能比较都是毫无意义的。 是的,二进制文件是用 -O2 编译的。 我突然想到一个想法。我相信 VM 中的时序信息可能会有所偏差,因为时钟中断没有以正确的频率传递。有人认为这可以解释这些结果吗? 【参考方案1】:

VM 中的速度与有效负载大小无关这一事实表明您做错了什么。这些不是完整的程序,因此很难确定是什么。使用 strace 查看发生了什么,即客户端是否确实发送了您认为的所有数据(并检查测试人员是否收到了应有的所有数据)。

100k READY/GO 对太多了;它基本上接近每秒上下文切换次数的上限,没有做任何其他事情。

【讨论】:

感谢您的想法。我同意 100k/sec 的速度非常快,而且我(令人愉快地)惊讶地发现性能几乎与有效载荷大小无关。但是,我很确定它正在获取所有数据。我已经让它打印出接收到的数据,并在调试器中运行它并查看了所有接收到的数据。另请注意,每台机器上都运行着完全相同的源代码(尽管已重新编译)。

以上是关于从标准输出读取的奇怪性能问题的主要内容,如果未能解决你的问题,请参考以下文章

编写一个从标准输入读取并打印到标准输出的 c 程序,没有内置的行长限制

试图从生成的 QProcess 中读取标准输出

Linux - 从管道读取的孩子接收发送到标准输出的调试消息

C语言,程序读取标准输入是啥意思?

QProcess -> 在 Linux 上从 sftp 读取标准输出

将标准输出字节从 Process 转发到 WebSocket 或读取当前 InputStream 长度