比较是否意味着分支?

时间:2015-01-14 16:04:48

标签: c++ optimization pipelining

我正在阅读有关优化的维基百科页面: http://en.wikibooks.org/wiki/Optimizing_C%2B%2B/Code_optimization/Pipeline 我遇到了这条线:

  

对于流水线处理器,比较比差异慢,因为它们意味着分支。

为什么比较意味着分支? 例如,如果:

int i = 2;
int x = i<5;

这是否有分支?对于带有条件的if语句进行分支是有意义的,但我不明白为什么比较单独会导致分支。

4 个答案:

答案 0 :(得分:3)

这只涉及一个分支:

unsigned(i – min_i) <= unsigned(max_i – min_i)

虽然这涉及两个:

min_i <= i && i <= max_i

当CPU遇到分支时,它会查询其预测变量并遵循最可能的路径。如果预测是正确的,那么分支在性能方面基本上是免费的。如果预测错误,CPU需要刷新管道并从头开始。

这种优化是一把双刃剑 - 如果你的分支是高度可预测的,那么第一个可能实际上比第二个慢。这完全取决于您对数据的了解程度。

答案 1 :(得分:3)

序言:现代编译器能够以各种方式消除分支。因此,没有一个示例必然会在最终(汇编程序或机器)代码中产生分支。

那么为什么逻辑基本上意味着分支?

代码

bool check_interval_branch(int const i, int const min_i, int const max_i)
{
  return min_i <= i && i <= max_i;
} 

可以在逻辑上重写为:

bool check_interval_branch(int const i, int const min_i, int const max_i)
{
  if (min_i <= i) 
  { 
    if (i < max_i) return true; 
  }
  return false;
} 

在这里你显然有两个分支(第二个分支仅在第一个分支为真时执行 - 短路),这可能被分支预测器错误预测,这反过来会导致重新分配管道。

Visual Studio 2013(优化转为1)生成包含check_interval_branch的两个分支的以下程序集:

  push  ebp
  mov   ebp, esp
  mov   eax, DWORD PTR _i$[ebp]
  cmp   DWORD PTR _min_i$[ebp], eax    // comparison
  jg    SHORT $LN3@check_inte          // conditional jump
  cmp   eax, DWORD PTR _max_i$[ebp]    // comparison
  jg    SHORT $LN3@check_inte          // conditional jump
  mov   al, 1
  pop   ebp
  ret   0
$LN3@check_inte:
  xor   al, al
  pop   ebp
  ret   0

代码

bool check_interval_diff(int const i, int const min_i, int const max_i)
{
  return unsigned(i - min_i) <= unsigned(max_i - min_i);
}

在逻辑上与

相同
bool check_interval_diff(int const i, int const min_i, int const max_i)
{
  if (unsigned(i – min_i) <= unsigned(max_i – min_i)) { return true; }
  return false;
}

只包含一个分支但执行两个差异。

Visual Studio 2013的check_interval_diff生成的代码甚至不包含条件跳转。

  push  ebp
  mov   ebp, esp
  mov   edx, DWORD PTR _i$[ebp]
  mov   eax, DWORD PTR _max_i$[ebp]
  sub   eax, DWORD PTR _min_i$[ebp]
  sub   edx, DWORD PTR _min_i$[ebp]
  cmp   eax, edx                    // comparison
  sbb   eax, eax
  inc   eax
  pop   ebp
  ret   0

(这里的诀窍是sbb完成的减法根据进位标志而不同,进位标志又由cmp指令设置为1或0。)

事实上,您在此处看到三个不同之处(2x sub,1x sbb)。

这可能取决于您的数据/用例哪一个更快。

请参阅Mysticals answer here关于分支预测。

您的代码int x = i<5;在逻辑上与

相同
int x = false;
if (i < 5)
{
  x = true;
}

再次包含一个分支(x = true仅在i < 5时执行。)

答案 2 :(得分:3)

虽然这里给出的答案是好的,但并非所有的比较都被转换为分支指令(它们确实会引入数据依赖性,这也可能会使您失去一些性能)。

例如,以下C代码

int main()
{
    volatile int i;
    int x = i<5;

    return x;
}

由gcc(x86-64,已启用优化)编译为:

    movl    -4(%rbp), %eax
    cmpl    $5, %eax
    setl    %al
    movzbl  %al, %eax

setl指令根据前面的比较指令的结果设置AL的值。

当然,这是一个非常简单的示例 - cmp/setl组合可能会引入阻止处理器并行执行它们的依赖关系,甚至可能会花费您几个周期。

但是,在现代处理器上,并非所有比较都转换为分支指令。

答案 3 :(得分:1)

谁写过那个页面并不能胜任程序员。第一, 比较必然意味着分支;这取决于你 和他们一起做。这是否意味着分支与否取决于 处理器和编译器。 if通常需要分支,但是 即便如此,一个好的优化者有时可以避免它。一个while或一个 除非编译器能够展开,否则for通常需要一个分支 循环,但该分支是高度可预测的,所以即使分支 预测是一个问题,可能无关紧要。

更一般地说,如果你在写作时担心这个级别的任何事情 你的代码,你浪费你的时间,并进行更多的维护 难。你应该关注的唯一一次是你有一个 性能问题,而分析器显示这是现场所在 你失去了表现。那时,你可以试验一下 几种不同的编写代码的方法,以确定哪一种 为您的编译器和硬件组合提供更快的代码 。 (更改编译器或硬件,它可能不是同一个。)