速度比较 - 模板专业化与虚拟功能与If-Statement的比较

时间:2010-05-13 23:21:04

标签: c++ optimization performance conditional vtable

只是为了让它脱离...

Premature optimization is the root of all evil

Make use of OOP

etc.

我明白。只是寻找一些关于我可以存储在灰色物质中的某些操作速度的建议,以备将来参考。

假设你有一个动画类。动画可以循环(一遍又一遍地播放)或不循环播放(播放一次),它可以具有独特的帧时间等等。假设这些“或”属性中有3个。 请注意,Animation类的任何方法最多都会检查其中一个(即,这不是if-elseif的巨大分支的情况)。

以下是一些选项。

1)为上面给出的属性赋予它布尔成员,并在播放动画时使用if语句检查它们以执行相应的操作。

  • 问题:每次播放动画时都会进行条件检查。

2)创建一个基础动画类,并派生其他动画类,如LoopedAnimation和AnimationUniqueFrames等。

  • 问题:Vtable检查每次调用动画,因为你有vector<Animation>之类的东西。此外,为所有可能的组合创建一个单独的类似乎代码膨胀。

3)使用模板专门化,并专门化那些依赖于这些属性的函数。像template<bool looped, bool uniqueFrameTimes> class Animation一样。

  • 问题:问题在于,您不能只为某些动画制作vector<Animation>。也可能很臃肿。

我想知道这些选项提供的速度是多少?我对第一个和第二个选项特别感兴趣,因为第三个选项不允许迭代Animation的一般容器。

简而言之,什么是更快 - vtable fetch或条件?

4 个答案:

答案 0 :(得分:4)

(1)并非现在生成的程序集的大小不再重要,但这就是它产生的结果(大约在x86上假设MSVC):

mov eax, [ecx+12]   ; 'this' pointer stored in ecx, eax is scratch
cmp eax, 0          ; test for 0 
jz  .somewhereElse  ; jump if the bool isn't set

优化编译器将在那里散布其他指令,使其更加管道友好。无论如何,你班级的内容很可能都在你的缓存中,如果不是,那么无论如何都需要几个周期。因此,回想起来,这可能是几个周期,对于每帧最多会调用几次的东西,这没什么。

(2)这大约是每次调用play()方法时生成的程序集:

mov  eax, [ebp+4]    ; pointer to your Animation* somewhere on the stack, eax is scratch
mov  eax, [eax+12]   ; dereference the vtable
call eax             ; call it

然后,你将在你的专业play()函数中有一些重复的代码或另一个函数调用,因为它们肯定会是一些常见的东西,因此会产生一些开销(代码大小和/或执行速度)。所以,这个速度要慢得多。

此外,这使得加载通用动画变得更加困难。你的图形部门不会高兴。

(3)为了有效地使用它,你最终会为你的模板化版本创建一个基类,使用虚函数(在这种情况下,参见(2)),或者你将通过检查类型手动完成在你调用动画的地方,在这种情况下也见(2)。

这也使得加载通用动画变得更加困难。你的图形部门会更不高兴。

(4)你需要担心的是,对于每帧最多几次完成的微小事情,不能进行一些微观优化。从阅读你的帖子,我实际上发现了另一个经常被忽视的问题。你提到std :: vector&lt; Animation&gt;。对STL没什么,但这是糟糕的伏都教。单个内存分配将比您的应用程序运行的整个时间内play()或update()方法中的所有布尔检查花费更多周期。将动画放入和放出std :: vectors(特别是如果你将实例而不是指针(聪明或愚蠢)放到实例中)将花费你更多。

您需要查看不同的地方进行优化。这是一个荒谬的微观优化,除了让你更难以概括并让你的图形部门感到高兴之外,它将带给你无益的好处。然而,重要的是担心内存分配,然后,当你完成编程部分,启动分析器并查看热点位置时。

如果保持动画实际上成为瓶颈,那么std :: vector(很好看)就是你想看的地方。你看过一个侵入性的链表吗?这实际上比担心这更有利。

答案 1 :(得分:3)

(为简洁起见。)

编译器,CPU和操作系统都可以改变答案,在这里:

  • CPU:指令/数据缓存大小,体系结构和行为,尤其是任何智能预取
  • CPU:分支预测和推测执行行为
  • CPU:错误预测分支的惩罚
  • 编译器和CPU:有条件执行指令的可用性和相对成本(有助于只包含少量指令的分支情况)
  • 编译器或链接器:可以转换代码并删除分支的优化

简而言之,正如Blindy在评论中所说:测试它。 =)

如果您正在为现代桌面操作系统或操作系统编写代码,请寻求分析工具(valgrind,shark,codeanalyst,vtune等)的帮助 - 它可能会为您提供您从未知道可以查找的详细信息,例如缓存未命中,分支错误预测等。

即使您没有找到一个好的答案,您也会从应用该工具中学到一些东西。我经常发现反汇编看起来非常有启发性(请参阅本主题中的其他一些答案)。

一些稍微更具投机性的笔记:

  • vtable倾向于导致加载(此+ 0),偏移,第二次加载,然后分支寄存器的内容。您可以在其他一些答案中看到这一点。我熟悉的大多数CPU都很难预测来自寄存器的分支。
  • bool可能接近您正在使用的其他数据,因此可能已经缓存。分支目标也可能是固定的,因此对预测和/或推测执行更加友好。
  • 在某些处理器上(现在比较少见),加载bool比使用int更费用。
  • 在我使用的ARM处理器上,我们偶尔将vtable塞进处理器内核的“紧耦合内存”中。大大减少间接加载时间 - 就好像vtable总是在缓存中或更好。

正如您所提到的,通常的规则适用:做什么符合要求,并且首先是灵活/可维护/可读,然后进行优化。

进一步阅读/追求其他模式:

“面向数据的设计”和“基于组件的实体”范例对于保持游戏,多媒体引擎以及其他对性能要求高于平均水平且仍然需要的东西非常有用保持你的代码有点组织。 YMMV,当然。 =)

答案 2 :(得分:2)

Vtable非常快。简单的条件也是如此。它们转换为CPU指令的单个数字。担心这种性能会让你陷入编译器优化的阴暗境界,你根本不了解编译器正在做什么。很可能,你的程序中非常微妙的变化可以胜过if语句和vtable之间的微小差异。

我在RTTI多次调度和vtable之间进行了一次while ago测试差异测试。在发布模式下,在两百万次迭代中完成的三个对象(两个vtable调用)之间的调度需要62毫秒。这种方式甚至不值得担心。

答案 3 :(得分:0)

谁说#3使得无法拥有动画的通用容器?有几种方法可以使用。它们都可以归结为最终进行多态调用,但选项就在那里。考虑一下:

std::vector<boost::any> generic_container;
function(generic_container[0]);

void function(boost::any & a)
{
  my_operation::execute(a.type().name(), a);
}

my_operation只需要按类型名称注册和过滤操作。它搜索一个对代表任何代表进行操作的仿函数,并使用它。然后,仿函数any_casts到适当的时间并进行类型特定的操作。

或使用访问者框架。以上是对此的一种变化,但过于通用的水平才真正有资格。

还有更多可能的方法。您可以存储隐藏特定内容的类型,并在激活时执行正确的视图选项,而不是存储动画。调用一个虚拟,但它特定于切换彼此执行更复杂操作的具体类型。

换句话说,你的问题没有一般性答案。根据您的需要,您可以达到各种复杂程度,使几乎整个程序的编译时间与运行时相比具有多态性。