【问题标题】:Is a pointer indirection more costly than a conditional?指针间接是否比条件更昂贵?
【发布时间】:2012-12-24 07:23:08
【问题描述】:

指针间接(获取值)是否比条件更昂贵?

我观察到大多数体面的编译器可以在不同程度上预先计算指针间接 - 可能会删除大多数分支指令 - 但我感兴趣的是间接成本是否大于分支成本指向生成的代码。

我希望如果指针引用的数据在运行时不在缓存中,则可能会发生缓存刷新,但我没有任何数据支持。

是否有人对此事有可靠的数据(或合理的意见)?


编辑: 几位发帖人指出,没有关于分支成本的“一般情况”:它因芯片而异。

如果您碰巧知道一个值得注意的案例,即分支会比缓存内间接更便宜(有或没有分支预测),请提及。

【问题讨论】:

  • 回答您在 PowerPC 上的编辑,正确预测的分支可以是零周期。它实际上比负载成本更低。

标签: c performance pointers conditional


【解决方案1】:

这真的取决于您的平台。不看目标 CPU 的内部,就没有一个正确的答案。我的建议是在测试应用中以两种方式测量它,看看是否有明显的差异。

我的直觉是,在现代 CPU 上,通过函数指针进行分支和条件分支都依赖于分支预测器的准确性,因此如果预测器具有相似的性能,我希望这两种技术具有相似的性能工作量。 (即,如果它总是以相同的方式结束分支,预计它会很快;如果难以预测,预计它会受到伤害。)但唯一确定的方法是在您的目标平台上运行真正的测试。

【讨论】:

  • 其实,当你说“指针间接”时,你真的是指“通过函数指针调用”吗?我将其解释为“从指针中读取值”。
  • StilesCrisis 的回答或多或少符合我的预期。分支成本因平台而异。我把问题放在那里看看是否有人已经做过这样的测试。
  • 好吧,老实说,我的回答基本没有改变。通过函数指针调用的成本也因 CPU 类型而异。在较旧的 CPU 上,它非常粗糙,但在现代 CPU 上,它与条件指令非常相似;两者都使用分支预测器,如果预测错误会变得昂贵。所以你真的需要在你的目标平台上测试它,看看预测器的表现如何。
  • 我应该在我的原始帖子中更具体;我感兴趣的是通过间接获取值的成本,而不是分支的成本。
  • 告诉你什么。为避免陷入困境,请发布您考虑的替代方案的两个示例(方法 A、方法 B)。
【解决方案2】:

由于处理器必须预测条件答案以计划哪条指令有更多的执行机会,我想说指令的实际成本并不重要。

条件指令效率低下,因为它们使流程变得不可预测。

【讨论】:

    【解决方案3】:

    这在很大程度上取决于具体情况。

    1 缓存(L1、L2、L3)中的数据多久一次,或者必须多久从 RAM 中一直提取一次?

    从 RAM 中提取大约需要 10-40ns。当然,这只会填满整个缓存行,所以如果你再使用接下来的几个字节,它肯定不会“受到严重伤害”。

    2 它是什么处理器?

    较旧的 Intel Pentium4 以其长流水线阶段而闻名,需要 25-30 个时钟周期(在 2GHz 时约为 15ns)才能从错误预测的分支中“恢复”。

    3 条件有多“可预测”?

    分支预测在现代处理器中确实很有帮助,它们也可以很好地处理“不可预测”的分支,但它确实有点伤害。

    4 缓存有多“忙”和“脏”?

    如果您必须丢弃一些脏数据来填充缓存行,则在“获取数据”时间之外还需要 15-50ns。

    间接本身将是一条快速指令,但当然,如果下一条指令立即使用数据,您可能无法立即执行该指令 - 即使数据在 L1 缓存中。

    另一方面,在美好的一天(准确预测、缓存中的目标、正确方向的风等),分支需要 3-7 个周期。

    最后,当然,编译器通常很清楚什么是最有效的...... ;)

    总而言之,很难确定,唯一的方法是判断哪种方法更适合您的情况,那就是对替代解决方案进行基准测试。我会认为间接内存访问比跳转更快,但是如果没有看到你的源代码编译成什么代码,很难说。

    【讨论】:

    • 您是否为您的统计数据考虑了特定的目标 CPU(例如您提到的 Pentium 4),或者您是否进行了有根据的猜测?你的数字看起来很合理,但如果你能更具体一些,它可能会让你的答案更有用。
    • 这些数字与任何现代数字都相差不远。它们都有相当长的流水线(20 个阶段以上),从内存中读取大约需要相同的时间。
    • 还要注意现代编译器经常避免分支,即使有一些分支,我多年前在 AMD 和 Intel 硬件 (P4) 上运行了一些测试以进行分支效率/分支预测。我创建了一个相当长的字符串(如果我没记错的话,32K),混合了大小写字符,然后计算了有多少个大写和小写。然后我对字符串进行了排序,速度提高了 4-7 倍。我还编写了一个“无分支”版本,其中包含一些汇编代码并使用不分支的条件指令来更新计数,它明显比排序变体快。
    • 我应该补充一点,我向 GCC 和 Microsoft 开发人员发送了那个小基准(我当时工作的地方与这些开发团队有合理的联系 - 我个人与 GCC x86 开发团队有联系) ,并且我已经用后来的编译器尝试了这个,他们在类似的代码中解决了“如何在没有分支的情况下做到这一点”[我这样“丢失”了我的基准测试,但我查看了“如果它是 A do”生成的代码x; else do y" 其中 x 和 y 是简单的操作。
    【解决方案4】:

    这取决于处理器,但取决于您正在处理的数据集,由错误预测的分支(或在某些情况下错误排序的指令)引起的管道刷新可能比简单的缓存未命中。

    例如,在 PowerPC 的情况下,未采用(但预计会采用)的分支花费大约 22 个周期(重新填充管道所花费的时间),而 L1 缓存未命中可能花费 600 个左右的内存周期。但是,如果您要访问连续数据,最好不要分支并让处理器缓存丢失您的数据,代价是 3 个周期(预计分支会被占用)对于您正在处理的每组数据。

    这一切都归结为:自己测试。对于所有问题,答案不是确定的。

    【讨论】:

    • 我明白这一点,并且我已经更改了问题以反映没有“一般情况”。特别感谢您关注 PowerPC 案例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 2016-11-05
    • 1970-01-01
    • 2016-10-26
    • 1970-01-01
    • 2012-03-23
    • 1970-01-01
    相关资源
    最近更新 更多