以非多态方式调用虚函数的代价是多少? [英] Whats the cost of calling a virtual function in a non-polymorphic way?

查看:167
本文介绍了以非多态方式调用虚函数的代价是多少?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个纯粹的抽象基础和两个派生类:

  struct B {virtual void foo() }; 
struct D1:B {void foo()override {cout< D1 :: foo()< endl; }};
struct D2:B {void foo()override {cout< D1 :: foo()< endl; }};

在A点调用 foo 同样调用非虚拟成员函数?或者是比D1和D2不是从B派生的更昂贵吗?

  int main(){
D1 d1; D2 d2;
std :: vector< B *> v = {& d1,& d2};

d1.foo(); d2.foo(); //点A(多态不必要)
for(auto&& i:v)i-> foo //多态性是必要的。

return 0;
}

答案 Prowl 是一种正确的答案,我只想添加gcc的汇编输出(在 godbolt :gcc-4.7 -O2 -march = native -std = c ++ 11)。直接函数调用的代价是:

  mov rdi,rsp 
调用D1 :: foo()
mov rdi,rbp
call D2 :: foo()

调用:

  mov rdi,QWORD PTR [rbx] 
mov rax,QWORD PTR [rdi]
调用[QWORD PTR [rax]]
mov rdi,QWORD PTR [rbx + 8]
mov rax,QWORD PTR [rdi]
call [QWORD PTR [rax]]
然而,如果对象不是从 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 *)

如果 D1 D2 不可以 启用进一步优化从 B 派生 c,所以我想,不,他们不是等效的(至少对于这个版本的gcc与这些优化,-O3产生了类似输出而不内联)。在 D1 D2 确实源自的情况下,是否有妨碍编译器内联的事情B



修复:使用代理(也称为虚拟函数自身重新实现):

  struct DG {// Delegate 
std :: function< void(void)> foo;
template< class C> DG(C& c){foo = [&](void){c.foo();}; }
};

然后创建一个委托向量:

  std :: vector< DG> v = {d1,d2}; 

如果以非多态方式访问方法,则允许内联。然而,我想访问向量将更慢(或至少一样快,因为 std :: function 使用虚拟函数类型擦除),而不仅仅是使用虚函数

解决方案


在A点调用foo的费用与调用相同到非虚拟成员函数?


是的。


还是比D1和D2不会从B中派生出来更昂贵?




编译器将静态解析这些函数调用,因为它们不是通过指针或通过引用执行的。由于在编译时调用函数的对象的类型是已知的,因此编译器知道必须调用 foo()的哪个实现。


I have a pure abstract base and two derived classes:

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; } };

Does calling foo in Point A cost the same as a call to a non-virtual member function? Or is it more expensive than if D1 and D2 wouldn't have derived from 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;
}

Answer: the answer of Andy Prowl is kind of the right answer, I just wanted to add the assembly output of gcc (tested in godbolt: gcc-4.7 -O2 -march=native -std=c++11). The cost of the direct function calls is:

mov rdi, rsp
call    D1::foo()
mov rdi, rbp
call    D2::foo()

And for the polymorphic calls:

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]]

However, if the objects don't derive from B and you just perform the direct call, gcc will inline the function calls:

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*)

This could enable further optimizations if D1 and D2 don't derive from B so I guess that no, they are not equivalent (at least for this version of gcc with these optimizations, -O3 produced a similar output without inlining). Is there something preventing the compiler from inlining in the case that D1 and D2 do derive from B?

"Fix": use delegates (aka reimplement virtual functions yourself):

struct DG { // Delegate
 std::function<void(void)> foo;
 template<class C> DG(C&& c) { foo = [&](void){c.foo();}; }
};

and then create a vector of delegates:

std::vector<DG> v = { d1, d2 };

this allows inlining if you access the methods in a non-polymorphic way. However, I guess accessing the vector will be slower (or at least as fast because std::function uses virtual functions for type erasure) than just using virtual functions (can't test with godbolt yet).

解决方案

Does calling foo in Point A cost the same as a call to a non-virtual member function?

Yes.

Or is it more expensive than if D1 and D2 wouldn't have derived from B?

No.

The compiler will resolve these function calls statically, because they are not performed through a pointer or through a reference. Since the type of the objects on which the function is called is known at compile-time, the compiler knows which implementation of foo() will have to be invoked.

这篇关于以非多态方式调用虚函数的代价是多少?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆