C++ iostream 与 C stdio 性能/开销

Dan*_*ons 5 c++ performance iostream

我试图理解如何提高这个 C++ 代码的性能,使其与它所基于的 C 代码相提并论。C 代码如下所示:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct point {
  double x, y;
} point_t;

int read_point(FILE *fp, point_t *p) {
  char buf[1024];
  if (fgets(buf, 1024, fp)) {
    char *s = strtok(buf, " ");
    if (s) p->x = atof(s); else return 0;
    s = strtok(buf, " ");
    if (s) p->y = atof(s); else return 0;
  }
  else
    return 0;
  return 1;
}

int main() {
  point_t p;
  FILE *fp = fopen("biginput.txt", "r");

  int i = 0;
  while (read_point(fp, &p))
    i++;

  printf("read %d points\n", i);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

C++ 代码如下所示:

#include <iostream>
#include <fstream>

using namespace std;

struct point {
  double x, y;
};

istream &operator>>(istream &in, point &p) {
  return in >> p.x >> p.y;
}

int main() {
  point p;
  ifstream input("biginput.txt");

  int i = 0;
  while (input >> p)
    i++;

  cout << "read " << i << " points" << endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我喜欢 C++ 代码更短更直接,但是当我在我的机器上运行它们时,我得到了非常不同的性能(两者都在同一台机器上针对 138 MB 测试文件运行):

$ time ./test-c
read 10523988 points
    1.73 real         1.68 user         0.04 sys
# subsequent runs:
    1.69 real         1.64 user         0.04 sys
    1.72 real         1.67 user         0.04 sys
    1.69 real         1.65 user         0.04 sys

$ time ./test-cpp
read 10523988 points
   14.50 real        14.36 user         0.07 sys
# subsequent runs
   14.79 real        14.43 user         0.12 sys
   14.76 real        14.40 user         0.11 sys
   14.58 real        14.36 user         0.09 sys
   14.67 real        14.40 user         0.10 sys
Run Code Online (Sandbox Code Playgroud)

连续多次运行任一程序不会改变 C++ 版本大约慢 10 倍的结果。

文件格式只是以空格分隔的双精度行,例如:

587.96 600.12
430.44 628.09
848.77 468.48
854.61 76.18
240.64 409.32
428.23 643.30
839.62 568.58
Run Code Online (Sandbox Code Playgroud)

有没有减少我缺少的开销的技巧?

编辑 1:使操作符内联似乎有一个非常小但可能检测到的影响:

   14.62 real        14.47 user         0.07 sys
   14.54 real        14.39 user         0.07 sys
   14.58 real        14.43 user         0.07 sys
   14.63 real        14.45 user         0.08 sys
   14.54 real        14.32 user         0.09 sys
Run Code Online (Sandbox Code Playgroud)

这并不能真正解决问题。

编辑2:我正在使用clang:

$ clang --version
Apple LLVM version 7.0.0 (clang-700.0.72)
Target: x86_64-apple-darwin15.5.0
Thread model: posix
Run Code Online (Sandbox Code Playgroud)

我没有在 C 或 C++ 上使用任何优化级别,而且它们都在我的 Mac 上使用相同版本的 Clang 进行编译。可能是 OS X 10.11 上的 Xcode (/usr/bin/clang) 附带的版本。我认为如果我在一个而不是另一个中启用优化或使用不同的编译器,这会使问题变得模糊。

编辑3:用istream &operator>>其他东西替换

我已经重写了 istream 运算符,使其更接近 C 版本,并且得到了改进,但我仍然看到了大约 5 倍的性能差距。

inline istream &operator>>(istream &in, point &p) {
  string line;
  getline(in, line);

  if (line.empty())
    return in;

  size_t next = 0;
  p.x = stod(line, &next);
  p.y = stod(line.substr(next));
  return in;
}
Run Code Online (Sandbox Code Playgroud)

运行:

$ time ./test-cpp
read 10523988 points
    6.85 real         6.74 user         0.05 sys
# subsequently
    6.70 real         6.62 user         0.05 sys
    7.16 real         6.86 user         0.12 sys
    6.80 real         6.59 user         0.09 sys
    6.79 real         6.59 user         0.08 sys
Run Code Online (Sandbox Code Playgroud)

有趣的是,编译它-O3是一个实质性的改进:

$ time ./test-cpp
read 10523988 points
    2.44 real         2.38 user         0.04 sys
    2.43 real         2.38 user         0.04 sys
    2.49 real         2.41 user         0.04 sys
    2.51 real         2.42 user         0.05 sys
    2.47 real         2.40 user         0.05 sys
Run Code Online (Sandbox Code Playgroud)

编辑 4:用 C 语言替换 istream 操作符的主体>>

这个版本非常接近 C 的性能:

inline istream &operator>>(istream &in, point &p) {
  char buf[1024];
  in.getline(buf, 1024);
  char *s = strtok(buf, " ");
  if (s)
    p.x = atof(s);
  else
    return in;

  s = strtok(NULL, " ");
  if (s)
    p.y = atof(s);

  return in;
}
Run Code Online (Sandbox Code Playgroud)

计时未优化让我们进入 2 秒区域,优化将其置于未优化的 C 之上(尽管优化的 C 仍然获胜)。准确地说,没有优化:

    2.13 real         2.08 user         0.04 sys
    2.14 real         2.07 user         0.04 sys
    2.33 real         2.15 user         0.05 sys
    2.16 real         2.10 user         0.04 sys
    2.18 real         2.12 user         0.04 sys
    2.33 real         2.17 user         0.06 sys
Run Code Online (Sandbox Code Playgroud)

和:

    1.16 real         1.10 user         0.04 sys
    1.19 real         1.13 user         0.04 sys
    1.11 real         1.06 user         0.03 sys
    1.15 real         1.09 user         0.04 sys
    1.14 real         1.09 user         0.04 sys
Run Code Online (Sandbox Code Playgroud)

带有优化的 C,只是为了做apples-to-apples:

    0.81 real         0.77 user         0.03 sys
    0.82 real         0.78 user         0.04 sys
    0.87 real         0.80 user         0.04 sys
    0.84 real         0.77 user         0.04 sys
    0.83 real         0.78 user         0.04 sys
    0.83 real         0.77 user         0.04 sys
Run Code Online (Sandbox Code Playgroud)

我想我可以接受这个,但作为一个 C++ 新手用户,我现在想知道是否:

  1. 是否值得尝试以另一种方式做到这一点?我不确定 istream 运算符内部发生的事情是否重要>>。
  2. 除了这三种方法之外,还有其他方法可以构建性能更好的 C++ 代码吗?
  3. 这是惯用语吗?如果不是,大多数人是否只是接受它的表现?

编辑 5:这个问题与关于 printf 的答案完全不同,我不明白链接的问题这应该是如何解决直接在此之上的三个点中的任何一个的重复。

Fre*_*k.L 4

导致性能显着差异的原因是整体功能的显着差异。

我将尽力详细比较这两种看似相同的方法。

在C中:

循环播放

  • 读取字符,直到检测到换行符或文件结尾或达到最大长度 (1024)
  • Tokenize 寻找硬编码的空白分隔符
  • 毫无疑问地解析为 double

在 C++ 中:

循环播放

  • 读取字符,直到检测到默认分隔符之一。这并不限制对实际数据模式的检测。以防万一,它将检查更多分隔符。到处都是开销。
  • 一旦找到分隔符,它将尝试优雅地解析累积的字符串。它不会假设您的数据中存在某种模式。例如,如果有 800 个连续的数字字符并且不再是该类型的良好候选者,则它必须能够自行检测到这种可能性,因此它会为此增加一些开销。

我建议的一种提高性能的方法与彼得在上述评论中所说的非常接近。使用getlineinsideoperator>>以便您可以讲述您的数据。像这样的东西应该能够恢复一些速度,认为它在某种程度上就像C-ing代码的一部分一样:

istream &operator>>(istream &in, point &p) {
    char bufX[10], bufY[10];
    in.getline(bufX, sizeof(bufX), ' ');
    in.getline(bufY, sizeof(bufY), '\n');
    p.x = atof(bufX);
    p.y = atof(bufY);
    return in;
}
Run Code Online (Sandbox Code Playgroud)

希望它有帮助。

编辑:应用nneonneo的评论

  • 每个人都在对我进行攻击,好像我在尝试对 iostreams 进行基准测试,或者好像我说过这两个程序是等效的,但我从来没有这么说过,只是说我有一个 C 程序并试图将它与 C++ 进行匹配程序。我接受并支持你,因为你的代码确实有效,而且你没有给我太多不相关的建议。就好像你真的读过我的问题一样。但这整个经历非常消极,我不知道该怎么办。 (2认同)