简体   繁体   English

派生类中虚函数调用的优化

[英]Optimization of virtual function calls in derived class

What is the best way to call virtual functions in a derived class so that the compiler can inline or otherwise optimize the call? 在派生类中调用虚函数的最佳方法是什么,以便编译器可以内联或以其他方式优化调用?

Example: 例:

class Base {
  virtual void foo() = 0;
};

class Derived: public Base {
  virtual void foo() {...}
  void bar() {
    foo();
  }
};

I want the call to foo() in bar() to always call Derived::foo() . 我想调用foo()bar()以随时拨打Derived::foo() It is my understanding that the call will result in a vtable lookup and the compiler cannot optimize it out since there may be another class inheriting from Derived. 我的理解是,调用将导致vtable查找,并且编译器无法优化它,因为可能有另一个继承自Derived的类。

I could explicitly call Derived::foo() but that gets verbose if there are many virtual function calls in Derived. 我可以显式调用Derived::foo()但是如果Derived中有许多虚函数调用则会变得冗长。 I also find it surprising that I could not find much material online addressing what seems to me to be a common case (a 'final' derived class calling virtual methods) so I wonder if I am misusing virtual functions here or excessively optimizing. 我也发现令人惊讶的是我找不到很多在线资料来解决我认为是常见的情况(一个'最终'派生类调用虚拟方法)所以我想知道我是在这里滥用虚函数还是过度优化。

How should this be done? 该怎么做? Stop prematurely optimizing and stick with foo() , suck it up and use Derived::foo() , or is there a better way? 停止过早优化并坚持使用foo() ,将其吸收并使用Derived::foo() ,还是有更好的方法?

C++11 contains the final keyword, which "specifies that a virtual function can not be overridden in a derived class or that a class cannot be inherited from." C ++ 11包含final关键字,它“指定不能在派生类中重写虚函数,或者不能继承类。” 1 . 1

It appears that g++ is able to optimize the virtual function call in the derived class if it has been declared final . 看来g ++能够优化派生类中的虚函数调用(如果它已被声明为final

I created the following test: 我创建了以下测试:

virtualFunctions.h

#pragma once
class Base {
public:
  virtual void foo();
  virtual void bar();
  virtual void baz();
  int fooVar, barVar, bazVar;
};
class Derived: public Base {
public:
  void test();
  virtual void foo();
  virtual void bar();
  virtual void baz() final;
};

virtualFunctions.cpp : virtualFunctions.cpp

#include "virtualFunctions.h"
void Derived::test() {
  foo();
  Derived::bar();
  baz();
}
void Derived::foo() {
  fooVar = 101;
}
void Derived::bar() {
  barVar = 202;
}
void Derived::baz() {
  bazVar = 303;
}

I am using g++ 4.7.2 and with -O1 the generated assembly contains: 我使用g ++ 4.7.2和-O1生成的程序集包含:

_ZN7Derived4testEv:
.LFB0:
    .loc 1 3 0
    .cfi_startproc
.LVL3:
    pushl   %ebx
.LCFI0:
    .cfi_def_cfa_offset 8
    .cfi_offset 3, -8
    subl    $24, %esp
.LCFI1:
    .cfi_def_cfa_offset 32
    movl    32(%esp), %ebx      ; Load vtable from the stack
    .loc 1 4 0
    movl    (%ebx), %eax        ; Load function pointer from vtable
    movl    %ebx, (%esp)
    call    *(%eax)             ; Call the function pointer
.LVL4:
    .loc 1 5 0
    movl    %ebx, (%esp)
    call    _ZN7Derived3barEv   ; Direct call to Derived::bar()
.LVL5:
    .loc 1 6 0
    movl    %ebx, (%esp)
    call    _ZN7Derived3bazEv   ; Devirtualized call to Derived::baz()

Derived::bar() and Derived::baz() were both called directly, while the vtable was used for foo() . Derived::bar()Derived::baz()都是直接调用的,而vtable则用于foo()

The compiler may be able to optimize it and perform devirtualization if it can statically find out what type is used. 如果编译器可以静态地找出使用的类型,则编译器可能能够对其进行优化并执行虚拟化。

Virtual method calls are quite cheap. 虚方法调用非常便宜。 Sometime ago I read an article stating that the overhead is roughly ten percent compared to a normal method call. 前段时间我读了一篇文章,说明与普通的方法调用相比,开销大约是10%。 This of course does not consider the missing inlining opportunity. 这当然不考虑缺少内联机会。

I also have a feeling that this mixes interface and implementation. 我也有一种感觉,这混合了界面和实现。 I think it would be better to split it into a pure interface and an implementation class. 我认为将它拆分为纯接口和实现类会更好。

As you yourself say, the performance impact of this should be your concern only in extreme rare cases. 正如您自己所说,只有在极少数情况下,这应该是您关心的性能影响。 If you are compiling as C++11 you can declare Derived and/or foo()/bar() as final and the compiler might inline it. 如果您正在编译为C ++ 11,则可以将Derived和/或foo()/bar()为final,编译器可能会将其内联。

The answer to the question is to disable dynamic dispatch, and that can be done through qualification: 这个问题的答案是禁用动态调度,这可以通过资格来完成:

class Derived: public Base {
  virtual void foo() {...}
  void bar() {
    Derived::foo();          // no dynamic dispatch
  }
};

Now the question is whether this is going to make a difference in performance (measure before changing things!) and whether it makes sense to do this. 现在的问题是,这是否会对性能产生影响(在改变之前采取措施!)以及是否有意义这样做。 A virtual function is an extension point for derived types. 虚函数是派生类型的扩展点。 If you disable dynamic dispatch, someone might create MoreDerived , implement foo and expect that bar calls MoreDerived::foo , but if you disabled dynamic dispatch that won't happen. 如果您禁用动态分派,有人可能会创建MoreDerived ,实现foo并期望该bar调用MoreDerived::foo ,但如果您禁用了不会发生的动态分派。

Unless there is a really good, measured, reason to try to micro-optimize this, avoid the problem altogether. 除非有一个非常好的,有计划的,尝试微观优化的理由,否则完全避免这个问题。 Chances are that if you run your code in a profiler the dynamic dispatch is not going to show up at all. 如果您在分析器中运行代码,动态调度根本不会显示。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM