我有这个简单的二元搜索成员函数,其中lastIndex
,nIter
和xi
是类成员:
uint32 scalar(float z) const
{
uint32 lo = 0;
uint32 hi = lastIndex;
uint32 n = nIter;
while (n--) {
int mid = (hi + lo) >> 1;
// defining this if-else assignment as below cause VS2015
// to generate two cmov instructions instead of a branch
if( z < xi[mid] )
hi = mid;
if ( !(z < xi[mid]) )
lo = mid;
}
return lo;
}
gcc和VS 2015都将内循环转换为代码流分支:
000000013F0AA778 movss xmm0,dword ptr [r9+rax*4]
000000013F0AA77E comiss xmm0,xmm1
000000013F0AA781 jbe Tester::run+28h (013F0AA788h)
000000013F0AA783 mov r8d,ecx
000000013F0AA786 jmp Tester::run+2Ah (013F0AA78Ah)
000000013F0AA788 mov edx,ecx
000000013F0AA78A mov ecx,r8d
有没有办法在不编写汇编内联的情况下说服他们使用1 comiss
条指令和2 cmov
条指令?
如果没有,有人可以建议如何为此编写gcc汇编程序模板吗?
请注意,我知道二进制搜索算法存在变化,编译器很容易生成无分支代码,但这不是问题所在。
由于
答案 0 :(得分:4)
正如评论中所说,虽然最近(> 4.4)版本的gcc似乎已经像你说的那样优化它,但是没有简单的方法可以强迫你提出要求。 编辑 :有趣的是,gcc 6 series seems to use a branch与使用两个cmov
的{{3}}和gcc 5系列不同。
通常__builtin_expect
可能无法推动gcc使用cmov
,因为cmov
通常很难预测比较结果,虽然__builtin_expect
告诉编译器可能的结果是什么 - 所以你只是把它推向错误的方向。
但是,如果您发现此优化非常重要,那么您的编译器版本通常会出错,并且出于某些原因您无法帮助它,相关的gcc程序集模板应该是这样的:
__asm__ (
"comiss %[xi_mid],%[z]\n"
"cmovb %[mid],%[hi]\n"
"cmovae %[mid],%[lo]\n"
: [hi] "+r"(hi), [lo] "+r"(lo)
: [mid] "rm"(mid), [xi_mid] "xm"(xi[mid]), [z] "x"(z)
: "cc"
);
使用的约束是:
hi
和lo
进入&#34;写&#34;变量列表,+r
约束为cmov
只能将寄存器用作目标操作数,我们有条件地只覆盖其中一个(我们不能使用=
,因为它意味着值总是被覆盖,所以编译器可以自由地给我们一个不同于当前目标寄存器的目标寄存器,并在我们的asm
块后用它来引用该变量;; mid
位于&#34;读取&#34; list,rm
as cmov
可以将寄存器或内存操作数作为输入值; xi[mid]
和z
位于&#34;读取&#34;列表;
z
具有特殊x
约束,表示&#34;任何SSE寄存器&#34; (ucomiss
第一个操作数需要); xi[mid]
有xm
,因为第二个ucomiss
操作数允许内存运算符;考虑到z
和xi[mid]
之间的选择,我选择最后一个作为直接从内存中获取的候选者,因为z
已经在寄存器中(由于系统V)调用约定 - 并且将在迭代之间进行缓存)并且xi[mid]
仅用于此比较; cc
(FLAGS寄存器)位于&#34; clobber&#34; list - 我们确实破坏了旗帜而没有别的。