c ++中的高性能代码(继承,函数指针,if)

时间:2013-11-14 13:44:47

标签: c++ performance inheritance optimization

假设您有一个非常大的图形,其节点上有大量处理(每个节点有数百万个操作)。每个节点的核心例程都是相同的,但是有一些基于内部条件的附加操作。可以有2个这样的条件产生4个案例(0,0),(1,0),(0,1),(1,1)。例如。 (1,1)表示两种条件都成立。条件在程序中建立一次(每个节点独立设置一个),从那时起,永远不会改变。不幸的是,它们是在运行时以完全不可预测的方式确定的(基于通过HTTP和外部服务器接收的数据)。

这种情况下最快的是什么? (考虑到我不知道的现代编译器优化)

  • 只需使用“IFs”:if(条件X)执行附加操作X。
  • 使用继承从基类派生四个类(暴露方法OPERATION)以进行适当的操作并节省数百万的“ifs”。 [但我不确定这是否真的存在,继承也必须有其开销)
  • 使用指向函数的指针根据条件分配函数一次。

我会花很长时间才能自己测试它(我还没有这么大的数据,这将被集成到更大的项目中,所以不容易测试所有版本)。

阅读答案:我知道我可能要尝试一下。但除了一切之外,这是一个更快的问题:

数百万的IF语句和正常的静态函数调用VS函数指针调用VS继承我认为这不是最好的想法,我想在进一步检查中消除它感谢任何建设性的答案(不是说我不应该关心这些小事;)

5 个答案:

答案 0 :(得分:3)

除了测量实际代码外,没有真正的答案 真实数据。有时,在过去,我不得不处理这样的问题 问题,以及我实际测量的情况,虚拟 功能比if更快。但这并不意味着什么, 因为我测量的案例是在一个不同的程序中(因此 一个不同的背景)比你的。例如,虚拟 函数调用通常会阻止内联,而if则是 本质上是内联的,内联可能会增加额外的内容 优化的可能性。

我测量的机器也处理虚拟功能 好;我听说过其他一些机器(例如HP的PA) 在实施间接跳跃方面非常无效 (不仅包括虚函数调用,还包括返回 从功能---再次,失去了内联的机会 费用)。

答案 1 :(得分:2)

如果您必须采用最快的方式,并且节点的流程顺序不相关,请制作四种不同的类型,每种情况一种,并为每种类型定义一个流程函数。然后在容器类中,有四个向量,每个节点类型一个。创建新节点后,获取创建节点所需的所有数据(包括条件),并创建正确类型的节点并将其推入正确的向量。然后,当您需要处理所有节点时,按类型处理它们,首先处理第一个向量中的所有节点,然后处理第二个等节点。

你为什么要这样做:

  • 状态切换没有ifs
  • 没有vtables
  • 无功能间接

但更重要的是:

  • 没有指令缓存颠簸(你没有跳到每个下一个节点的代码的不同部分)
  • 没有分支预测错过状态切换ifs(因为没有)

即使您具有虚函数的继承并因此通过vtable进行间接操作,只需在向量中按类型对节点进行排序可能已经在性能上产生了不同的世界,因为任何可能的指令高速缓存颠簸基本上都会消失。根据分支预测的方法,也可以减少分支预测丢失。

另外,不要制作指针矢量,而是制作一个对象矢量。如果它们是指针,你有一个额外的地址间接,这本身并不令人担心,但问题是如果对象几乎随机地遍布你的内存,它可能会导致数据缓存颠簸。另一方面,如果您的对象直接放入向量中,处理将基本上线性地通过内存,并且缓存将基本上每次都被命中,并且缓存预取可能实际上能够做得很好。

请注意,如果你没有正确地执行数据结构创建,你会付出很大的代价,如果可能的话,当向量为你的所有节点立即保留足够的容量时,每次你的向量重新分配和移动耗尽空间可能会变得昂贵。

哦,是的,正如詹姆斯提到的那样,永远都要衡量!您认为可能是最快的方式可能不是,有时候事情非常直观,取决于各种因素,如优化,流水线,分支预测,缓存命中/未命中,数据结构布局等。我上面写的是一个非常一般的方法,但它不能保证是最快的,并且肯定有办法做错了。测量,测量,测量。

P.S。使用虚函数继承大致相当于使用函数指针。虚函数通常由类头部的vtable实现,它基本上只是一个函数指针表,用于实现给定虚拟对象的实际类型。 ifs是否比虚拟更快或反过来是一个非常非常难以回答的问题,完全取决于所使用的实现,编译器和平台。

答案 2 :(得分:1)

我实际上对分支预测的有效性印象非常深刻,只有 if 解决方案允许内联也可能是戏剧性的。虚函数和函数指针也涉及从内存加载,可能导致缓存未命中

但是,你有四个条件,所以分支失误可能很昂贵。

没有能力测试和验证答案真的无法回答。特别是因为它甚至不清楚这将是一个足以保证优化工作的性能瓶颈。

在这种情况下。我会在可读性和易于调试方面犯错,并使用 if

答案 3 :(得分:1)

许多程序员已经开始上课并阅读有关某些最喜欢的主题的书籍:流水线,缓存,分支预测,虚函数,编译器优化,大O算法等等以及这些的表现。

如果我可以对划船进行类比,那么这些就像削减重量,调整力量,调整平衡和精简一样,假设你从一些已经接近最佳状态的快艇开始。

别介意你可能实际上是从<玛丽女王开始,而你假设它是一艘快艇。

很可能有很多方法可以通过大量因素来加速代码,只需要减少脂肪(伪装成好的设计),只要你知道它在哪里。 好吧,你不知道它在哪里,并猜测浪费时间在哪里,除非你重视错误。

当人们说“测量和使用剖析器”时,他们指向正确的方向,但还不够远。 Here's an example of how I do itI made a crude video of it, FWIW.

答案 4 :(得分:0)

除非这些属性有明确的模式,否则不存在可以为您有效预测此数据相关条件的分支预测变量。在这种情况下,您可能最好避免控制推测(并支付分支错误预测的代价),并等待实际数据到达并解决控制流(更可能使用虚函数发生)。当然,您必须进行基准测试以验证它,因为它取决于实际模式(例如,如果您甚至有类似“标记”元素的小组)。

上面建议的排序很好,但请注意,它将一个简单的O(n)问题转换为O(logn)问题,因此对于大尺寸,除非你可以排序一次,否则你会输掉 - 遍历许多时间,或以其他方式廉价维持排序状态。

请注意,某些预测变量也可能会尝试预测函数调用的地址,因此您可能会面临同样的问题。

但是,我必须同意有关早期优化的评论 - 您是否确定控制流程是您的瓶颈?如果从内存中获取实际数据需要更长时间,该怎么办?一般来说,你的元素似乎可以并行处理,所以即使你在一个线程上运行它(如果你使用多个核心就更多) - 你应该是带宽限制而不是延迟限制。