在性能方面,虚拟方法和std :: function成员变量相比如何?

13

游戏引擎有这个类:

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 = 鼠标侦听器)。如果知道该类来自外部库(静态链接),从性能角度来看,哪个更好?

注意:除非接收到鼠标事件,否则不会调用这些函数中的任何一个函数;当发生鼠标事件时,针对每个监听鼠标输入的对象调用相应的函数(不应该很多,少于50个)


5
我已经摆脱了调用虚函数的额外开销,这是错误的;事实上,您增加了至少一个间接步骤。 std::function内部同样使用虚函数实现。编译器必须从隐藏在std::function中的指针中提取vtable并进行间接调用,而不是检索类vtable插槽并进行间接调用。作为额外的缺点,std::function非常复杂,以至于我从未见过编译器对其进行任何解虚拟化处理,在许多情况下它会对“普通”的虚函数进行处理。 - Matteo Italia
"我摆脱了开销"。这样大胆的声明不能没有分析,你之前和之后有对代码进行过分析吗? - StoryTeller - Unslander Monica
不,源代码非常庞大,我还没有做任何更改,我需要先了解一些答案。 - Dhia Hassen
@iMoses,你能否澄清一下原因? - Dhia Hassen
1
那是1年前,我运行了引擎的分析器,发现std::function更好,因为它避免了额外的调用。 - Dhia Hassen
显示剩余4条评论
1个回答

11

这实际上取决于虚函数和函数对象的使用方式。

尽管std::function可能比虚调用慢*,但是std::function具有短缓冲区优化,这可以避免动态内存分配(通常会在虚函数版本中出现)。这本身可能比您使用普通多态性能更快。

在内部(但不能保证),std::function对象无论如何都使用虚拟调用进行类型抹除,因此我认为差异微不足道。

提示-确保通过调用if(static_cast<bool>(myFunction))检查std::function是否有效。编译器将插入检查以查看函数是否为空。如果不为空,程序将抛出std::bad_function_call。开发者检查将使编译器在优化打开时删除其检查和与std::bad_function_call相关的代码,留下更加平滑的汇编代码。

在处理性能和C++时,在我的经验中,优化内存分配、线程间争用和与高速缓存不良工作的数据结构相比,优化CPU部分(如虚函数vs.std::function)要重要得多。

*一个不错的编译器可以将类型抹除实现为虚函数+给定的lambda作为内联函数。理论上,它不应该比常规虚函数慢。另一方面,如果函数对象得到一个不可内联的可调用对象,例如一个函数指针,则可能使用两个间接引用来启动函数。这可能比常规虚拟调用慢。它取决于情况。


3
我现在和原帖作者一样陷入了同样的困境,但由于重写OnMouseDown通常是可选的,如果你有1000个对象,只有200个覆盖它,那么你将会对虚拟OnMouseDown进行1000次调用。然而,如果你有一个std::function(或一个普通的c函数指针),你可以检查它是否为空,不会造成调用开销,这应该会使事情更快。然而,分支条件是半调用(跳跃),但由于它们是短跳跃,如果我没记错的话,它们需要大约5个周期。虚拟调用90%的时间将是远程调用。 - Gam
这就是为什么我问这个问题的原因。 - Dhia Hassen
2
请注意:您不需要使用 if (static_cast<bool>(myFunction)),因为在 C++11 中,有一种称为“显式转换运算符”的上下文转换。这意味着在某些情况下(如 if、while、&& 等),运算符变得隐式,您只需使用 if (myFunction) 即可。 - Adrian B.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接