gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 20:22:25 25 4
gpt4 key购买 nike

我正在尝试了解如何提高此 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;
}

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;
}

我喜欢 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

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

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

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

是否有减少我遗漏的开销的技巧?

编辑 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

这并不能真正解决问题。

编辑 2:我正在使用 clang:

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

我没有在 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;
}

运行:

$ 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

有趣的是,用 -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

编辑 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;
}

未优化的时间让我们进入了 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

与:

    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

经过优化的 C,只是为了做同类:

    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

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

  1. 值得尝试换一种方式吗?我不确定 istream 运算符内部发生的事情是否重要>>。
  2. 除了这三种方式之外,是否还有另一种构建性能更好的 C++ 代码的方式?
  3. 这是惯用语吗?如果不是,大多数人会接受表演的本来面目吗?

编辑 5:这个问题与关于 printf 的答案完全不同,我看不出链接问题 this is supposedly a duplicate of addresses 正上方的三个点中的任何一个。

最佳答案

造成性能显着差异的是整体功能的显着差异。

我会尽我所能详细比较你们这两种看似等效的方法。

在 C 中:

循环

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

在 C++ 中:

循环

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

我建议的一种提高性能的方法与 Peter 在上述评论中所说的非常接近。在 operator>> 中使用 getline,这样您就可以了解您的数据。像这样的东西应该能够让你的速度恢复一些,认为它有点像 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;
}

希望对您有所帮助。

编辑:应用了 nneonneo 的评论

关于C++ iostream 与 C stdio 性能/开销,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37894262/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com