【问题标题】:Cache miss penalty on branching分支时的缓存未命中惩罚
【发布时间】:2014-04-30 08:48:29
【问题描述】:

我想知道用 2 次乘法替换分支是否更快(由于缓存未命中惩罚)?
这是我的情况:

float dot = rib1.x*-dir.y + rib1.y*dir.x;

if(dot<0){
    dir.x = -dir.x;
    dir.y = -dir.y;
}

我正在尝试将其替换为:

float dot = rib1.x*-dir.y + rib1.y*dir.x;

int sgn = (dot  < 0.0) - (0.0 < dot ); //returns -1 or 1 (no branching here, tested)
dir.x *= sgn;
dir.y *= sgn;

【问题讨论】:

  • 您为什么不对其进行基准测试并告诉我们您发现了什么?
  • 我担心在我的带有 8Mb 缓存的 i7 上,我永远不会在这个测试中出现缓存未命中。
  • 如果它不会发生,那有什么关系呢? ;) 我假设您想针对具有较小缓存的内核来证明这一点?为什么不简单地用海量数据集进行测试,比你的 i7 处理的还要大?
  • 分支的问题不在于缓存未命中,而在于中断instruction pipeline。而且,顺便说一句,当它说“8Mb”的缓存时,那是 L3 缓存,它只是引用总容量,而缓存未命中与通常约为 64 字节的 缓存行 相关(至少,在 i7 上是)。
  • 顺便说一下,全球 50% 的概率不提供可预测性信息。可以很好地预测 20 次被录取,然后 20 次未被录取(通常为 90%)。使用“循环”预测器,如果分支始终在采用和未采用之间交替(即 T,NT,T,NT,T,NT,...),预测将接近 100%。我宁愿怀疑 FP 条件移动会比你的整数评估和 FP 乘法更快。一些 SIMD 指令集还提供比较,如果为真,则设置数据元素中的所有位,左移 32 位和异或将(我相信)有条件地否定。

标签: c++ performance cpu cpu-cache branch-prediction


【解决方案1】:

乘法的成本取决于几个因素,您是使用 32 位还是 64 位浮点数,以及是否启用 SSE。根据此来源,两次浮点乘法的成本为 10 个周期:http://www.agner.org/optimize/instruction_tables.pdf

分支机构的成本还取决于几个因素。根据经验,不要担心代码中的分支。 CPU 上的分支预测器的确切行为将定义性能,但在这种情况下,您可能应该期望分支最多是不可预测的,因此这可能会导致很多分支错误预测。根据此来源,分支错误预测的成本为 10-30 个周期:http://valgrind.org/docs/manual/cg-manual.html

任何人都可以在这里给出的最佳建议是分析和测试。我猜想在现代 Core i7 上,两个乘法应该比分支 if the range of input varies sufficiently as to cause sufficient branch mispredictions as to outweigh the cost of the additional multiplication 更快。

假设未命中率为 50%,则分支的成本平均为 15 个周期 (30 * 0.5),浮点 mul 的成本为 10 个周期。


编辑:添加链接,更新估计指令成本。

【讨论】:

  • 假设没有 SSE 和 50% 的分支误预测率。分支错误预测大约为 18 个周期。浮点乘法大约需要 10 个周期。
  • @fixxer - 据此valgrind.org/docs/manual/cg-manual.html 分支错误预测为 10-30 个周期。根据这个agner.org/optimize/instruction_tables.pdf,它的 2 float mul 大约需要 10 个周期。无论如何 30*.5 = 15(分支)与 10(mul)。万一这不占 50%.... 我会继续分支。谢谢。回答这个问题,我会接受的。
  • 我已经更新了我的答案,感谢您提供的链接。
  • 单精度FP乘法一般需要4个周期(DP,5个周期),两次乘法不依赖,所以5个周期即可完成(DP为6个)。两个整数比较可以并行执行并且只需要 1 个周期,整数减法会增加另一个周期,但是 dot 从浮点数到整数和 sgn 从整数到浮点数的转换可能会降低性能。
  • 感谢您的澄清。
【解决方案2】:

分支并不意味着缓存未命中:只有指令预取/流水线受到干扰,因此您可能会在编译时阻止一些 SSE 优化。

另一方面,如果只使用 x86 指令,speculative execution 将让处理器正确地开始执行最常用的分支。

另一方面,如果您在 50% 的情况下输入 if,那么您处于最坏的情况:在这种情况下,我会尝试寻找 SSE 流水线并使用 SSE 优化执行,可能会得到来自this post 的一些提示,与您的第二个代码块一致。

但是,请对您的代码进行基准测试,检查生成的汇编程序,以便为这种优化找到最佳解决方案,并获得正确的见解。并最终让我们更新:)

【讨论】:

  • 我们在这里宣扬同样的事情:测量两次,切割一次。
  • 是的! - 如果他的代码能够熟练使用 SSE,我认为他将能够从第二个代码中获得更多东西。但实际上,这在很大程度上取决于数据量、缓存的使用……今天的架构中涉及的因素太多了!
  • 假设我(和我的编译器)不使用 SSE。假设分支进入了 50% 的次数。在最坏的情况下,它只会这样做“dir.x = -dir.x; dir.y = -dir.y;”当这是不必要的(浪费2-4个周期)?还是不行?
  • 我认为在这种情况下,您应该拥有这种情况,以防 1. 分支预测器预测一半的时间和流水线较少的指令,另一半,流水线被错误预测 - 在 2. 流水线没有被错误预测破坏,但是要执行的指令很少。这两种情况在效率方面非常相似。重要的是管道的哪些阶段是免费的,有足够的“数据压力”(即 - 数据已经在 L1 缓存中),......所以你需要再次测试它。如果结果相同,我不会感到惊讶。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-23
  • 2021-09-27
相关资源
最近更新 更多