尝试用类组合理解运行时性能,我编写了以下测试代码。在其中,我将直接调用函数所花费的时间与类的成员函数进行比较,而不是通过将原始类作为成员的复合类来调用它。
似乎方法应该花费相当的时间,但它们不会:通过复合类调用几乎需要两倍的时间。
以下是代码:
const int REPS(1e8);
const double INPUT(5.29);
class Base {
public:
inline double BaseFunc(double x) const;
};
double Base::BaseFunc(double x) const {
return 2.718*x + 3.14;
};
class Super {
public:
inline double BaseFunc(double x) const;
private:
Base b_;
};
double Super::BaseFunc(double x) const {
return b_.BaseFunc(x);
};
int main() {
auto t0 = std::chrono::high_resolution_clock::now();
// Construct objects.
Base b;
Super s;
// Call to base directly.
for (int i = 0; i < REPS; ++i)
b.BaseFunc(INPUT);
auto t1 = std::chrono::high_resolution_clock::now();
// Call to base through composited class.
for (int i = 0; i < REPS; ++i)
s.BaseFunc(INPUT);
auto t2 = std::chrono::high_resolution_clock::now();
// Find average durations.
auto diff1 = std::chrono::duration_cast<std::chrono::nanoseconds>(t1-t0).count();
diff1 /= REPS;
auto diff2 = std::chrono::duration_cast<std::chrono::nanoseconds>(t2-t1).count();
diff2 /= REPS;
std::cout << "Calling directly to base took " << diff1 << "nsec.\n";
std::cout << "Calling to base through a composited class took " << diff2 << "nsec.\n";
}
使用g ++版本4.7.2进行编译,使用-std = c ++ 11 -O0 -Winline,我得到:
Calling directly to base took 13nsec.
Calling to base through a composited class took 24nsec.
为什么这两种调用基本相同功能的方式之间存在这种差异?我认为,因为所有内容都是内联的(gcc没有告诉我),这些应该是相同的。
我是否完全错误地考虑了这一点?任何帮助表示赞赏!谢谢!
更新感谢您的帮助!我回去并在函数调用中放了更多(在向量上调用inner_product)并且每次重复使用所有结果,以确保gcc没有优化任何东西。然后我开启了优化。你们都是对的:差别消失了。
我认为我学到了两件重要的事情:1)关闭优化后,gcc甚至不会尝试进行内联,所以-Winline标志没有意义,2)没有任何有意义的区别在这两种方式之间调用函数。我可以自信地从其他类中调用成员函数!
再次感谢!
答案 0 :(得分:6)
该程序未经优化编译(-O0
)。这意味着我们不能指望通常的代码质量。为了找出问题所在,我们需要查看生成的代码。可能没有发生内联(尽管使用inline
请求)。这可能使运行时调用的数量增加了一倍,因此运行时间大约增加了一倍。
如果程序已正确优化,则两个循环都将被完全删除。我认为这个基准是没有实际意义的,无论结果如何,它都没有意义。在生产中,将执行完全优化和实际工作量。
答案 1 :(得分:1)
inline
只是提示编译器,而非要求。事实上,大多数编译器都完全忽略它。
您提供了编译器参数-O0
,禁用了所有优化。由于内联是一种优化,因此不会发生。这意味着调用开销发生两次 - 解释几乎是运行时的两倍。
答案 2 :(得分:0)
要查看函数调用是否内联,您可以查看生成的汇编代码。在Linux上,您可以使用objdump -d
来执行此操作。如果你不想涉及所有的装配,如果你知道你在寻找什么,你可以寻找有意义的东西:
objdump -d a.out | egrep "(main|BaseFunc|call)" | egrep "(main|BaseFunc)" | egrep -v "(jmp|jne)"
(是的,如果重写,那几行egrep正则表达式几乎肯定会被缩短,但这不是重点。)
如果函数未内联,结果将类似于:
08048700 <__libc_start_main@plt>:
804879c: e8 5f ff ff ff call 8048700 <__libc_start_main@plt>
080488c0 <main>:
804896e: e8 65 02 00 00 call 8048bd8 <_ZNK4Base8BaseFuncEd>
80489b5: e8 50 02 00 00 call 8048c0a <_ZNK5Super8BaseFuncEd>
08048bd8 <_ZNK4Base8BaseFuncEd>:
08048c0a <_ZNK5Super8BaseFuncEd>:
8048c30: e8 a3 ff ff ff call 8048bd8 <_ZNK4Base8BaseFuncEd>
在main
内注意对名为BaseFunc()
(Base::BaseFunc()
和Super::BaseFunc()
)的函数的两次调用。
我将您的-O0
更改为-O1
然后我看到了类似的内容......
08048660 <__libc_start_main@plt>:
80486dc: e8 7f ff ff ff call 8048660 <__libc_start_main@plt>
08048800 <main>:
...现在你可以看出两个函数调用都是内联的,并且(在这种情况下,很可能)甚至REPS循环本身可能已被优化掉了,因为在我的系统上,两个结果都是0ns。
为防止优化循环,我添加了这个全局变量:
volatile long gCtr = 0;
...在函数Base::BaseFunc()
中,我在return
之前添加了这个:
gCtr += 1;
......现在两者的结果都是2ns,这看起来很可信。两个BaseFunc()
函数都是内联的,但循环没有被优化掉。