【问题标题】:std::cin really slowstd::cin 真的很慢
【发布时间】:2016-02-12 23:36:56
【问题描述】:

所以我试图为自己编写一个用于 linux 管道的命令。可以把它想象成 gnu 'cat' 或 'sed' 的复制品,它从标准输入中获取输入,进行一些处理并写入标准输出。

我最初编写了一个 AWK 脚本,但想要更高的性能,所以我使用了以下 c++ 代码:

std::string crtLine;
crtLine.reserve(1000);
while (true)
{
    std::getline(std::cin, crtLine);
    if (!std::cin) // failbit (EOF immediately found) or badbit (I/O error)
        break;

    std::cout << crtLine << "\n";
}

这正是 cat (没有任何参数)所做的。 事实证明,这个程序与 awk 对应的程序差不多慢,但远不及 cat。

在 1GB 文件上进行测试:

$time cat 'file' | cat | wc -l
real    0m0.771s

$time cat 'file' | filter-range.sh | wc -l
real    0m44.267s

我尝试了 cin.getline(buffer, size) 而不是 getline(istream, string) 但没有任何改进。这很尴尬,是缓冲问题吗?我还尝试一次获取 100KB 而不是一行,没有帮助!有什么想法吗?

编辑: 你们说的有道理,但罪魁祸首不是字符串构建/复制,也不是扫描换行符。 (缓冲区的大小也不是)。看看这两个程序:

char buf[200];
while (fgets(buf, 200, stdin))
    std::cout << buf;

$time cat 'file' | ./FilterRange > /dev/null
real    0m3.276s




char buf[200];
while (std::cin.getline(buf, 200))
    std::cout << buf << "\n";

$time cat 'file' | ./FilterRange > /dev/null
real    0m55.031s

它们都不操作字符串并且都进行换行扫描,但是其中一个比另一个慢 17 倍。它们的区别仅在于使用 cin。 我认为我们可以有把握地得出结论,cin 搞砸了时机。

【问题讨论】:

  • filter-range.sh 中还有什么?你为什么不直接调用你的 C++ 程序呢?此外,该循环的典型模式是 while(std::getline(std::cin, crtLine)) { std::cout &lt;&lt; crtLine &lt;&lt; "\n"; },但更改它不会影响您的问题。
  • 如果你追求性能,你应该尝试 C 风格的 I/O 函数而不是 cin/cout ;)
  • 你编译优化了吗? -O2 还是 -O3?这可能不会减少 44 秒,但如果您担心时间问题,绝对应该这样做。
  • Rob:是的,你是对的,你的版本是等效的并且更漂亮。我使用它,直接调用脚本,没有改变,我的程序什么也不做。我正在使用 g++ -O3 -Wall -c -fmessage-length=0 -MMD -MP
  • 您会惊讶于您的通用命令比专门设计用于快速执行某些操作的专用工具要慢。如果你写了一个命令,那就是甜菜猫,那么它就是新的猫。由于您正在进行行处理,我希望您(在您进行大量优化之后)获得与任何基于行的 unix 过滤器相同的速度。

标签: c++ pipeline cout cin


【解决方案1】:

首先要让标准 I/O 流对象获得良好性能,它会关闭与标准 C 流对象的同步:

std::ios_base::sync_with_stdio(false);

完成此操作后,您应该会获得更好的性能。但是,您是否获得良好的表现是另一个问题。

由于有些人声称 cat 会在里面做什么有趣的事情,这里应该是复制一个流到另一个流的最快方法:

std::cout << std::cin.rdbuf();

如果您可以正确地std::copy() 一个流到另一个流,我会很高兴,但这对于大多数 I/O 流实现来说效果不佳:

std::copy(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>(),
          std::ostreambuf_iterator<char>(std::cout));

我希望我最终能做到最好……

【讨论】:

  • 确实,sync_with_stdio 似乎很有帮助。它将速度提高了 7 倍
  • 实际上,这是黄金,因为它使 cin 只比 fgets 慢 2 倍,还有一个额外的好处是你不需要知道最大行长度......像这样的任何“优化” ? :)
  • 不,没有这么简单的事情。在某个地方,我有自己的 IOStreams 实现,它做了很多有趣的优化,但它的不完整状态现在对你没有帮助。不过,我仍然希望能在某个时候启动它...... ;)
【解决方案2】:

这正是 cat (没有任何参数)所做的。

不是真的。这和/bin/cat的效果一模一样,只是使用的方法不同。

/bin/cat 看起来更像这样:

while( (readSize = read(inFd, buffer, sizeof buffer)) > 0)
  write(outFd, buffer, readSize);

请注意,/bin/cat 不会对其输入进行处理。它不会从中构建std::string,也不会扫描\n,它只是一个接一个地执行系统调用。

另一方面,您的程序构建strings,复制它们,扫描\n,等等。

这个小而完整的程序运行速度比 /bin/cat 慢 2-3 个数量级:

#include <string>
#include <iostream>

int main (int ac, char **av) {
  std::string crtLine;
  crtLine.reserve(1000);
  while(std::getline(std::cin, crtLine)) {
    std::cout << crtLine << "\n";
  }
}

我是这样计时的:

$ time ./x < inputFile > /dev/null
$ time /bin/cat < inputFile > /dev/null


编辑 该程序的性能在 /bin/cat 的 50% 以内:
#include <string>
#include <iostream>
#include <vector>

int main (int ac, char **av) {
  std::vector<char> v(4096);
  do {
    std::cin.read(&v[0], v.size());
    std::cout.write(&v[0], std::cin.gcount());
  } while(std::cin);
}

简而言之,如果您的要求是对输入进行逐行分析,那么您将不得不付出一些代价来使用格式化输入。另一方面,如果您需要执行逐字节分析,那么您可以使用未格式化的输入并更快。

【讨论】:

  • 我将此标记为答案,但也要阅读原始问题中的编辑
【解决方案3】:

如果您真的希望使用 stdin 获得更好的性能,您应该尝试使用纯 C。

vector<char> line(0x1000);
while(!feof(stdin))
    fgets(&line.front(), line.size(), stdin);

【讨论】:

    【解决方案4】:

    我认为更快的解决方案将基于sendfile

    【讨论】:

      猜你喜欢
      • 2017-11-10
      • 2010-12-28
      • 2013-01-25
      • 1970-01-01
      • 2012-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多