我实现了一些主数据结构是树的算法。我使用一个类来表示一个节点和一个表示树的类。因为节点得到了很多更新,所以我称之为许多setter和getters。
因为我多次听说函数调用很昂贵,所以我想,如果我使用结构表示节点和树,它会使我的算法在实践中更有效。
在此之前,我决定进行一项小型实验,看看实际情况是否如此。
我创建了一个有一个私有变量,一个setter和一个getter的类。此外,我创建了一个具有一个变量的结构,没有setter / getter,因为我们可以通过调用struct.varName
来更新变量。结果如下:
运行次数是我们调用setter / getter的次数。以下是实验代码:
#include <iostream>
#include <fstream>
#define BILLION 1000000000LL
using namespace std;
class foo{
private:
int a;
public:
void set(int newA){
a = newA;
}
int get(){
return a;
}
};
struct bar{
int a;
};
timespec startT, endT;
void startTimer(){
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &startT);
}
double endTimer(){
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &endT);
return endT.tv_sec * BILLION + endT.tv_nsec - (startT.tv_sec * BILLION + startT.tv_nsec);
}
int main() {
int runs = 10000000;
int startRun = 10000;
int step = 10000;
int iterations = 10;
int res = 0;
foo f;
ofstream fout;
fout.open("stats.txt", ios_base::out);
fout<<"alg\truns\ttime"<<endl;
cout<<"First experiment progress: "<<endl;
int cnt = 0;
for(int run = startRun; run <= runs; run += step){
double curTime = 0.0;
for(int iter = 0; iter < iterations; iter++) {
startTimer();
for (int i = 1; i <= run; i++) {
f.set(i);
res += f.get();
}
curTime += endTimer()/iterations;
cnt++;
if(cnt%10 == 0)
cout<<cnt/(((double)runs-startRun+1)/step*iterations)*100<<"%\r";
}
fout<<"class\t"<<run<<"\t"<<curTime/BILLION<<endl;
}
int res2 = 0;
bar b;
cout<<"Second experiment progress: "<<endl;
cnt = 0;
for(int run = startRun; run <= runs; run += step){
double curTime = 0.0;
for(int iter = 0; iter < iterations; iter++) {
startTimer();
for (int i = 1; i <= run; i++) {
b.a = i;
res2 += b.a;
}
curTime += endTimer()/iterations;
cnt++;
if(cnt%10 == 0)
cout<<cnt/(((double)runs-startRun+1)/step*iterations)*100<<"%\r";
}
fout<<"struct\t"<<run<<"\t"<<curTime/BILLION<<endl;
}
fout.close();
cout<<res<<endl;
cout<<res2<<endl;
return 0;
}
我不明白为什么我会这样做。我认为功能调用更贵?
编辑:我在没有-O3的情况下重新运行相同的实验
编辑:好的,这非常令人惊讶,通过在一个名为foo.h
的单独文件中声明该类,在foo.cpp
中实现getter / setter并使用-O3运行,似乎该类变得均匀更低效。
答案 0 :(得分:10)
我多次听说函数调用很贵。
这是1970年的任何机会吗?
编译器很聪明。很聪明。他们生成了他们可以提供源代码的最佳程序,除非你做的事非常奇怪,否则这些类型的设计更改不太可能产生太多(如果有的话)性能差异。
最值得注意的是,在大多数情况下,简单的getter / setter甚至可以完全内联(除非你做了一些奇怪的事情),让你的两个程序在编译后有效地相同!您可以在图表上看到此结果。
与此同时,将class
替换为struct
的具体更改对性能没有任何影响 - 两个关键字都定义了类。
我不明白为什么我会这样做。我认为功能调用更贵?
看,这就是我们不过早优化的原因。编写清晰,易于阅读的代码,无需技巧,让编译器完成剩下的工作。这是它的工作,而且它通常非常擅长。
答案 1 :(得分:3)
这里的答案几乎肯定是编译器优化。首先,在类定义中定义getter和setter使它们成为inline。但是,即使你没有这样做,我希望任何现代编译器都能优化掉函数调用,如果它们在同一个文件中并且编译器知道结果对象是整个程序。