我想测量和比较不同函数调用的开销.在处理扩展类的同时最小化代码修改的两种替代方法的意义上是不同的:
使用抽象基类并在虚拟成员函数中提供实现
使用策略宿主类并使用静态和成员函数定义不同的策略
将这两个选项与根本不调用任何函数进行比较.我也知道在设计支持动态多态的类时通常使用的NVI习惯用法 - 我使用的示例只是开销的基准.
这是我试图用于此目的的代码:
#include#include #include #include #include class Interface { public: virtual double calculate(double t) = 0; virtual ~Interface() = default; }; class Square : public Interface { public: double calculate(double d) { return d*d; } }; class SquareStaticFunction { public: static double calculate(double d) { return d*d; } }; class SquareMemberFunction { public: double calculate(double d) { return d*d; } }; template class Generic : public Function { public: using Function::calculate; }; using namespace std; int main(int argc, const char *argv[]) { vector test(1e06, 5); unique_ptr sUptr(new Square()); Interface* sPtr = new Square(); Generic gStatic; Generic gMember; double result; typedef std::chrono::high_resolution_clock Clock; auto start = Clock::now(); for (auto d : test) { result = d * d; } auto end = Clock::now(); auto noFunction = end - start; start = Clock::now(); for (auto d : test) { result = sUptr->calculate(d); } end = Clock::now(); auto virtualMemberFunction = end - start; start = Clock::now(); for (auto d : test) { result = sPtr->calculate(d); } end = Clock::now(); auto virtualMemberFunctionRaw = end - start; start = Clock::now(); for (auto d : test) { result = gStatic.calculate(d); } end = Clock::now(); auto staticPolicy = end - start; start = Clock::now(); for (auto d : test) { result = gMember.calculate(d); } end = Clock::now(); auto memberPolicy = end - start; cout << noFunction.count() << " " << virtualMemberFunction.count() << " " << virtualMemberFunctionRaw.count() << " " << staticPolicy.count() << " " << memberPolicy.count() << endl; delete sPtr; sPtr = nullptr; return 0; }
我使用gcc 4.8.2编译代码,并在Linux x86_64机器上编译代码,具有以下CPU型号:Intel(R)Core(TM)i7-4700MQ CPU @ 2.40GHz.
通过原始指针在一次测试中访问虚拟成员函数,在另一次测试中通过a访问虚拟成员函数unique_ptr
.首先,我编译了代码而没有任何优化:
g++ -std=c++11 main.cpp -o main
并使用以下shell命令运行1000次测试:
for i in {1..1000}; do ./main >> results; done
我使用以下gnuplot
脚本绘制的结果文件(注意对数y轴):
set terminal png size 1600,800 set logscale y set key out vert right top set out 'results.png' plot 'results' using 0:1 title "no function" , \ 'results' using 0:2 title "virtual member function (unique ptr)", \ 'results' using 0:3 title "virtual member function (raw ptr)", \ 'results' using 0:4 title "static policy", \ 'results' using 0:5 title 'member function policy'
对于非优化代码,该图如下所示:
Q1对虚拟函数的调用unique_ptr
最终是最昂贵的,因为它在解除引用托管对象的指针时涉及重定向?
然后我打开优化并编译代码:
g++ -std=c++11 -O3 main.cpp -o main
结果如下图所示:
Q2:在这种情况下,虚拟成员的功能是否成本最高,因为当通过基类指针或引用访问时(vtable调度打开),编译器不可能使它们内联?
问题3:这个问题让我发布了所有这些:在优化的图表中,静态和成员策略最终如何比这个简单示例的推出代码更快?
编辑:在result
volatile
启用优化的情况下进行编译和编译,使策略的运行时间更长,但它们与原始乘法代码类似:
编辑修改代码,以便将结果添加到而不是分配(由注释中的dyk提议),而不使用 volatile
:
result += ...
结果与原始代码的图表相同.
-O3 -march=native -std=c++11
查看代码的反汇编表明编译器通过检测对同一个未使用变量的不必要的重新实现来进行"太多"优化.正如评论中所建议的,我用的是+=
代替=
.我也初始化result = 0
并main
返回result
而不是0
确保编译器计算其值.此修改后的代码给出:
noFunction
,staticPolicy
和memberPolicy
降低为mulsd
,addsd
,addsd
,即标量SSE指令.Clang也没有矢量化(使用vanilla选项),但Intel的icc确实(它根据对齐和迭代计数生成矢量和非矢量版本和跳转).
virtualMemberFunction
并virtualMemberFunctionRaw
导致动态函数调用(无去内核和内联)
你可以在这里粘贴你的代码来亲眼看看.
要回答Q1"指针vs unique_ptr
调试版本":在-O0
调用中没有自动内联,特别unique_ptr::operator->
是在没有内联的情况下显式调用,因此对于常规指针,每次迭代调用2次而不是1次.对于优化的构建,这种差异消失了
要回答你的Q2"可以内联虚拟调用":在这个例子中,gcc和clang不会内联调用,因为它们可能没有做足够的静态分析.但你可以帮助他们.例如,使用clang 3.3(但不是3.2而不是gcc)将方法声明为const
并__attribute((pure))
完成工作.在gcc(4.8,pre-4.9)中,我尝试将方法标记为final
并编译,-fwhole-program
但这并没有删除调用.所以,在这种特定情况下,可以去虚拟化,但不可靠.通常,jitted编译器(C#,Java)在去虚拟化方面更胜一筹,因为它们可以从运行时信息中做出更好的假设.