我正在努力理解如何改善这个基于C++的代码的性能,使其与基于C的代码保持一致。C代码如下:
#include
#include
#include
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
#include
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++版本大约比Python版本慢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
我在我的Mac上使用相同版本的Clang编译C和C++,没有使用任何优化级别。很可能使用的是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:将istream运算符>>的主体替换为C语言代码
这个版本已经接近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 operator>>内部发生什么事情是否重要。
除了以下三种方式外,是否有另一种构建C++代码的更好方法?
这种用法是否符合惯用法?如果不是,大多数人是否只接受其性能?
编辑5:这个问题与printf回答完全不同,我不明白所谓的重复链接如何直接解决上面提到的三个问题。