【发布时间】:2016-06-18 07:04:50
【问题描述】:
我正在尝试了解如何提高此 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 倍的结果。
文件格式只是以空格分隔的双精度行,例如:
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++ 新手,我现在想知道:
- 是否值得尝试以另一种方式执行此操作?我不确定 istream 运算符内部发生的事情是否重要>>。
- 除了这三种方法之外,还有其他方法可以构建性能更好的 C++ 代码吗?
- 这是惯用语吗?如果不是,大多数人是否只是接受表演的本来面目?
编辑 5:这个问题与关于 printf 的答案完全不同,我看不出链接的问题这应该是如何解决正上方三个点中的任何一个问题。
【问题讨论】:
-
这些例子不是比较喜欢的。 C++ 版本从流对象中读取每个值,并且可以与
fscanf("%f &f ", &p->x, &p->y)之类的使用相媲美。 C 版本是手工制作的,通过直接读取一行然后解析该行来击败这种fscanf()调用的性能。 C++ 版本可以类似地设计为使用std::getline()或std::istream::getline(),然后解析字符串。 -
@Peter 我认为没有人在实践中使用 fscanf;它非常脆弱。但我很高兴做出建议的更改以使用
getline— 之后下一步是什么,创建一个字符串流并从中使用>>而不是 iostream? -
@Peter 请注意,我并不是想在这里创建一个完美的基准,实际上,作为 C++ 的新手,我对在实践中加速一些 C++ 代码很感兴趣。
-
我在评论您的代码所比较的内容 - 不提倡使用或避免使用
fscanf()。但是,您拥有的 C++ 代码在功能上等同于fscanf()(除了需要解析格式字符串),尽管不那么脆弱。读取后解析字符串的一种方法是使用std::istrstream。还有其他的。 -
抱歉,比较未优化二进制文件的时间是毫无意义的。根据经验,c++ 函数通常有更多的间接层,如果您激活优化,编译器会完全优化这些层。
标签: c++ performance iostream