gnz*_*lbg 9 c++ performance virtual-functions
我有一个纯粹的抽象基础和两个派生类:
struct B { virtual void foo() = 0; };
struct D1 : B { void foo() override { cout << "D1::foo()" << endl; } };
struct D2 : B { void foo() override { cout << "D1::foo()" << endl; } };
Run Code Online (Sandbox Code Playgroud)
foo在A点呼叫的成本是否与对非虚拟成员功能的呼叫相同?或者它是否比D1和D2不是从B派生的更昂贵?
int main() {
D1 d1; D2 d2;
std::vector<B*> v = { &d1, &d2 };
d1.foo(); d2.foo(); // Point A (polymorphism not necessary)
for(auto&& i : v) i->foo(); // Polymorphism necessary.
return 0;
}
Run Code Online (Sandbox Code Playgroud)
答案:Andy Prowl的答案是正确答案,我只想添加gcc的汇编输出(在godbolt中测试:gcc-4.7 -O2 -march = native -std = c ++ 11).直接函数调用的成本是:
mov rdi, rsp
call D1::foo()
mov rdi, rbp
call D2::foo()
Run Code Online (Sandbox Code Playgroud)
对于多态调用:
mov rdi, QWORD PTR [rbx]
mov rax, QWORD PTR [rdi]
call [QWORD PTR [rax]]
mov rdi, QWORD PTR [rbx+8]
mov rax, QWORD PTR [rdi]
call [QWORD PTR [rax]]
Run Code Online (Sandbox Code Playgroud)
但是,如果对象不是从派生的B,你只是执行直接调用,gcc将内联函数调用:
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
Run Code Online (Sandbox Code Playgroud)
这可能使进一步的优化,如果D1并D2没有从派生B,所以我想这不,他们是不等价的(至少在这个版本的GCC与这些优化,-O3生产无内联类似的输出).有没有什么东西阻止编译器内联D1并D2从中派生出来B?
"修复":使用委托(又称自己重新实现虚拟功能):
struct DG { // Delegate
std::function<void(void)> foo;
template<class C> DG(C&& c) { foo = [&](void){c.foo();}; }
};
Run Code Online (Sandbox Code Playgroud)
然后创建一个委托向量:
std::vector<DG> v = { d1, d2 };
Run Code Online (Sandbox Code Playgroud)
如果以非多态方式访问方法,则允许内联.但是,我想访问该向量将比std::function使用虚函数更慢(或至少同样快,因为使用虚函数进行类型擦除)(不能使用godbolt进行测试).
在A点调用foo与调用非虚拟成员函数的成本是否相同?
是.
或者它是否比D1和D2不是从B派生的更昂贵?
没有.
编译器将静态解析这些函数调用,因为它们不是通过指针或通过引用执行的.由于调用函数的对象类型在编译时是已知的,因此编译器知道foo()必须调用哪个实现.
最简单的解决方案是查看编译器的内部结构。canDevirtualizeMemberFunctionCall在 Clang 中,我们在lib/CodeGen/CGClass.cpp中找到:
/// canDevirtualizeMemberFunctionCall - Checks whether the given virtual member
/// function call on the given expr can be devirtualized.
static bool canDevirtualizeMemberFunctionCall(const Expr *Base,
const CXXMethodDecl *MD) {
// If the most derived class is marked final, we know that no subclass can
// override this member function and so we can devirtualize it. For example:
//
// struct A { virtual void f(); }
// struct B final : A { };
//
// void f(B *b) {
// b->f();
// }
//
const CXXRecordDecl *MostDerivedClassDecl = getMostDerivedClassDecl(Base);
if (MostDerivedClassDecl->hasAttr<FinalAttr>())
return true;
// If the member function is marked 'final', we know that it can't be
// overridden and can therefore devirtualize it.
if (MD->hasAttr<FinalAttr>())
return true;
// Similarly, if the class itself is marked 'final' it can't be overridden
// and we can therefore devirtualize the member function call.
if (MD->getParent()->hasAttr<FinalAttr>())
return true;
Base = skipNoOpCastsAndParens(Base);
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Base)) {
if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
// This is a record decl. We know the type and can devirtualize it.
return VD->getType()->isRecordType();
}
return false;
}
// We can always devirtualize calls on temporary object expressions.
if (isa<CXXConstructExpr>(Base))
return true;
// And calls on bound temporaries.
if (isa<CXXBindTemporaryExpr>(Base))
return true;
// Check if this is a call expr that returns a record type.
if (const CallExpr *CE = dyn_cast<CallExpr>(Base))
return CE->getCallReturnType()->isRecordType();
// We can't devirtualize the call.
return false;
}
Run Code Online (Sandbox Code Playgroud)
我相信代码(以及附带的注释)是不言自明的:)
| 归档时间: |
|
| 查看次数: |
537 次 |
| 最近记录: |