【问题标题】:Virtual method VS std::function member variable in terms of performance虚拟方法 VS std::function 成员变量在性能方面
【发布时间】:2017-05-03 16:28:46
【问题描述】:

游戏引擎有这个类:

class  MouseListener{
public :
MouseListener();
virtual void OnMouseDown(int mx,int my);
virtual void OnMouseUp(int mx,int my);
.
.
.
};

每个想要监听鼠标输入的对象,都必须继承该类并覆盖它的方法。为了不必每次都声明一个新类型,该类被修改为:

class  MouseListener{
public :
MouseListener();
std::function <void(MouseListener*,int,int)>OnMouseDown;
std::function <void(MouseListener*,int,int)>OnMouseUp;
.
.
.
};

现在可以通过这种方式使用该类:

MouseListener * m = new MouseListener();
m->OnMouseDown = [](MouseListener * thiz,int x,int y){
   //// do something
};

从输入系统中,只有不为 null(已分配)的 MouseListener 函数被调用(其中 thiz = 鼠标侦听器)。 知道该类是从外部库(静态链接)中使用的,这在性能方面更好吗?

注意:除非收到鼠标事件,否则不会调用这些函数,当发生这种情况时,会为每个侦听鼠标输入的对象调用适当的函数(不应该是很多,

【问题讨论】:

  • "我摆脱了调用虚函数的开销" false;您实际上添加了至少一个间接步骤。 std::function 在内部也是用虚函数实现的。编译器必须从隐藏在std::function 中的指针中提取vtable 并对其进行间接调用,而不是检索您的类vtable 槽并进行间接调用。作为一个额外的缺点,std::function 是一个非常复杂的野兽,我从未见过编译器对其进行任何去虚拟化,而在许多情况下它是在“常规”虚拟函数上完成的。
  • “我摆脱了开销” 如此大胆的声明不能不进行 profiling,你之前和之后都对你的代码进行了 profile 吗?
  • 不,源代码很大,我还没有改变任何东西,我需要先知道一些答案
  • 坚持虚拟
  • 那是一年前的事,我确实运行了引擎的分析器,发现 std::function 更好,因为它避免了额外的调用

标签: c++ performance inheritance lambda virtual-functions


【解决方案1】:

这真的取决于虚函数与函数对象的用法。

虽然std::function 可能比虚拟调用慢*,std::function 具有短缓冲区优化,这可能会阻止动态内存分配(您可能会在虚拟版本中)。 这本身可能会胜过您使用常规多态性所做的任何事情。

在内部(但不能保证),std::function 对象无论如何都使用虚拟调用来进行类型擦除,所以我认为差异可以忽略不计。

提示 - 确保检查 std::function 是否有效(通过调用 if(static_cast&lt;bool&gt;(myFunction)))。编译器将插入检查函数是否为空。如果不是,程序将抛出std::bad_function_call。开发人员检查将使编译器在打开优化时删除他的检查以及与std::bad_function_call 相关的代码,从而留下许多“更流畅”的汇编代码。

根据我的经验,在处理性能和 C++ 时,优化内存分配、线程间争用和不利于缓存的不良数据结构更为重要。通常,它比优化 CPU 的东西更有价值(比如虚函数 vs.std::function

*一个体面的编译器可以将类型擦除实现为虚函数 + 给定的 lambda 作为内联函数。理论上,它不应该比普通的虚函数慢。另一方面,如果函数对象得到一个不可内联的可调用对象,比如函数指针,它可能会使用两次间接调用来启动函数。这可能比普通的虚拟通话要慢。这取决于。

【讨论】:

  • 我现在面临与 OP 相同的困境,但由于覆盖 OnMouseDown 通常是可选的,如果您有 1000 个对象,并且只有 200 个对象覆盖它,您将对虚拟 OnMouseDown。但是,如果您有一个 std::function 代替(或一个普通的 c 函数指针),您可以检查它是否为空,并且不要占用调用开销,这应该会使事情变得更快。然后再次分支条件是半调用(跳转),但由于它们是短跳转,如果我是正确的,它们需要 5 个周期。虚拟通话 90% 的时间都是 FAR 通话。
  • 这就是我问这个问题的原因
  • 注意:你不需要if (static_cast&lt;bool&gt;(myFunction)),因为在C++11中,explicit operator bool() const有一个叫做上下文转换的东西。这意味着在某些情况下(if、while、&&、...),运算符变得隐式,你可以只做if (myFunction)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-30
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 2011-03-31
  • 2017-01-18
相关资源
最近更新 更多