比较是否意味着分支?
Is it the case that comparisons imply a branch?
我正在阅读有关优化的维基百科页面:
http://en.wikibooks.org/wiki/Optimizing_C%2B%2B/Code_optimization/Pipeline
我遇到了这条线:
For pipelined processors, comparisons are slower than differences, because they imply a branch.
为什么比较意味着分支?
例如,如果:
int i = 2;
int x = i<5;
这里有分店吗?对带有条件的 if 语句进行分支对我来说很有意义,但我不明白为什么单独比较会导致分支。
这只涉及一个分支:
unsigned(i – min_i) <= unsigned(max_i – min_i)
虽然这涉及两个:
min_i <= i && i <= max_i
当 CPU 遇到分支时,它会参考其预测器并遵循最可能的路径。如果预测正确,则该分支在性能方面基本上是免费的。如果预测错误,CPU 需要冲洗管道并重新开始。
这种优化是一把双刃剑 --- 如果您的分支是高度可预测的,第一个实际上可能 运行 比第二个慢。这完全取决于您对数据的了解程度。
序言:现代编译器能够以各种方式消除分支。因此,none 的示例必然导致最终(汇编程序或机器)代码中的分支。
那么为什么逻辑基本上暗示分支?
密码
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(优化转为一)生成以下程序集,其中包含 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
完成的减法根据进位标志相差 1,进位标志又被 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
时执行。)
虽然这里给出的答案很好,但并不是所有的比较都被翻译成分支指令(它们确实引入了数据依赖性,这也可能会降低您的性能)。
例如下面的C代码
int main()
{
volatile int i;
int x = i<5;
return x;
}
由 gcc(x86-64,启用优化)编译成:
movl -4(%rbp), %eax
cmpl , %eax
setl %al
movzbl %al, %eax
setl
指令根据其前面的比较指令的结果设置AL
的值。
当然,这是一个非常简单的示例——cmp/setl
组合可能引入了依赖项,这些依赖项会阻止处理器并行执行它们,甚至可能会花费您几个周期。
不过,在现代处理器上,并不是所有的比较都变成分支指令。
写过那个页面的人不称职。第一的,
比较 并不 必然暗示一个分支;这取决于你
和他们一起做。这是否意味着一个分支取决于
处理器和编译器。 if
通常需要一个分支,但是
即使那样,一个好的优化器有时也可以避免它。一个 while
或一个
for
通常需要一个分支,除非编译器可以展开
循环,但该分支是高度可预测的,所以即使分支
预测是个问题,可能无关紧要。
更一般地说,如果您在写作时担心这个级别的任何事情
你的代码,你在浪费你的时间,并使维护工作变得更多
难的。你唯一应该关心的是一旦你有一个
性能问题,探查器显示这是
你正在失去表现。那时,您可以尝试
几种不同的代码编写方式,以确定哪一种将
为编译器和硬件的组合 生成更快的代码。
(换个编译器或硬件,可能就不是原来的了)
我正在阅读有关优化的维基百科页面: http://en.wikibooks.org/wiki/Optimizing_C%2B%2B/Code_optimization/Pipeline 我遇到了这条线:
For pipelined processors, comparisons are slower than differences, because they imply a branch.
为什么比较意味着分支? 例如,如果:
int i = 2;
int x = i<5;
这里有分店吗?对带有条件的 if 语句进行分支对我来说很有意义,但我不明白为什么单独比较会导致分支。
这只涉及一个分支:
unsigned(i – min_i) <= unsigned(max_i – min_i)
虽然这涉及两个:
min_i <= i && i <= max_i
当 CPU 遇到分支时,它会参考其预测器并遵循最可能的路径。如果预测正确,则该分支在性能方面基本上是免费的。如果预测错误,CPU 需要冲洗管道并重新开始。
这种优化是一把双刃剑 --- 如果您的分支是高度可预测的,第一个实际上可能 运行 比第二个慢。这完全取决于您对数据的了解程度。
序言:现代编译器能够以各种方式消除分支。因此,none 的示例必然导致最终(汇编程序或机器)代码中的分支。
那么为什么逻辑基本上暗示分支?
密码
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(优化转为一)生成以下程序集,其中包含 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
完成的减法根据进位标志相差 1,进位标志又被 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
时执行。)
虽然这里给出的答案很好,但并不是所有的比较都被翻译成分支指令(它们确实引入了数据依赖性,这也可能会降低您的性能)。
例如下面的C代码
int main()
{
volatile int i;
int x = i<5;
return x;
}
由 gcc(x86-64,启用优化)编译成:
movl -4(%rbp), %eax
cmpl , %eax
setl %al
movzbl %al, %eax
setl
指令根据其前面的比较指令的结果设置AL
的值。
当然,这是一个非常简单的示例——cmp/setl
组合可能引入了依赖项,这些依赖项会阻止处理器并行执行它们,甚至可能会花费您几个周期。
不过,在现代处理器上,并不是所有的比较都变成分支指令。
写过那个页面的人不称职。第一的,
比较 并不 必然暗示一个分支;这取决于你
和他们一起做。这是否意味着一个分支取决于
处理器和编译器。 if
通常需要一个分支,但是
即使那样,一个好的优化器有时也可以避免它。一个 while
或一个
for
通常需要一个分支,除非编译器可以展开
循环,但该分支是高度可预测的,所以即使分支
预测是个问题,可能无关紧要。
更一般地说,如果您在写作时担心这个级别的任何事情 你的代码,你在浪费你的时间,并使维护工作变得更多 难的。你唯一应该关心的是一旦你有一个 性能问题,探查器显示这是 你正在失去表现。那时,您可以尝试 几种不同的代码编写方式,以确定哪一种将 为编译器和硬件的组合 生成更快的代码。 (换个编译器或硬件,可能就不是原来的了)