Pat*_*rin 10 c optimization x86 gcc
是否有gcc编译指示或我可以用来强制gcc在特定代码段上生成无分支指令?
我有一段代码,我希望gcc使用cmov指令编译成无分支代码:
int foo(int *a, int n, int x) {
int i = 0, j = n;
while (i < n) {
#ifdef PREFETCH
__builtin_prefetch(a+16*i + 15);
#endif /* PREFETCH */
j = (x <= a[i]) ? i : j;
i = (x <= a[i]) ? 2*i + 1 : 2*i + 2;
}
return j;
}
Run Code Online (Sandbox Code Playgroud)
事实上,它确实如此:
morin@soprano$ gcc -O4 -S -c test.c -o -
.file "test.c"
.text
.p2align 4,,15
.globl foo
.type foo, @function
foo:
.LFB0:
.cfi_startproc
testl %esi, %esi
movl %esi, %eax
jle .L2
xorl %r8d, %r8d
jmp .L3
.p2align 4,,10
.p2align 3
.L6:
movl %ecx, %r8d
.L3:
movslq %r8d, %rcx
movl (%rdi,%rcx,4), %r9d
leal (%r8,%r8), %ecx # put 2*i in ecx
leal 1(%rcx), %r10d # put 2*i+1 in r10d
addl $2, %ecx # put 2*i+2 in ecx
cmpl %edx, %r9d
cmovge %r10d, %ecx # put 2*i+1 in ecx if appropriate
cmovge %r8d, %eax # set j = i if appropriate
cmpl %esi, %ecx
jl .L6
.L2:
rep ret
.cfi_endproc
.LFE0:
.size foo, .-foo
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)
(是的,我意识到循环是一个分支,但我在谈论循环中的选择运算符.)
不幸的是,当我启用__builtin_prefetch
调用时,gcc会生成分支代码:
morin@soprano$ gcc -DPREFETCH -O4 -S -c test.c -o -
.file "test.c"
.text
.p2align 4,,15
.globl foo
.type foo, @function
foo:
.LFB0:
.cfi_startproc
testl %esi, %esi
movl %esi, %eax
jle .L7
xorl %ecx, %ecx
jmp .L5
.p2align 4,,10
.p2align 3
.L3:
movl %ecx, %eax # this is the x <= a[i] branch
leal 1(%rcx,%rcx), %ecx
cmpl %esi, %ecx
jge .L11
.L5:
movl %ecx, %r8d # this is the main branch
sall $4, %r8d # setup the prefetch
movslq %r8d, %r8 # setup the prefetch
prefetcht0 60(%rdi,%r8,4) # do the prefetch
movslq %ecx, %r8
cmpl %edx, (%rdi,%r8,4) # compare x with a[i]
jge .L3
leal 2(%rcx,%rcx), %ecx # this is the x > a[i] branch
cmpl %esi, %ecx
jl .L5
.L11:
rep ret
.L7:
.p2align 4,,5
rep ret
.cfi_endproc
.LFE0:
.size foo, .-foo
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)
我已尝试使用__attribute__((optimize("if-conversion2")))
此功能,但这没有任何效果.
我非常关心的原因是我手工编辑了编译器生成的无分支代码(来自第一个例子)以包含prefetcht0指令,它运行速度比gcc生成的两个版本快得多.
如果您真的依赖于该级别的优化,则必须编写自己的汇编程序存根.
原因是,即使代码中的其他地方进行了修改,也可能会更改编译器(不是特定于gcc)发出的代码.另外,不同版本的gcc,不同的选项(例如-fomit-frame-pointer)可以显着改变代码.
如果必须的话,你真的应该这样做.其他影响可能会产生更大的影响,如缓存配置,内存分配(DRAM页面/库),执行顺序与并发运行程序相比,CPU关联等等.首先使用编译器优化.您可以在文档中找到命令行选项(您没有发布使用的版本,因此不具体).
一个(严重的)替代方案是使用clang/llvm.或者只是帮助gcc团队改进他们的优化器.你不会是第一个.另请注意,gcc在上一版本中专门针对ARM进行了大量改进.
看起来 gcc 可能无法在循环条件和后置条件中使用的变量生成无分支代码,以及在伪函数内部调用中保持临时寄存器处于活动状态的约束。
有一些可疑之处,使用 -funroll-all-loops 和 -fguess-branch-probability 时,您的函数生成的代码是不同的。我生成了许多返回指令。它闻起来像是 gcc 中的一个小错误,围绕着编译器的 rtl 传递,或者代码块的简化。
以下代码在两种情况下都是无分支的。这将是向 GCC 提交错误的一个很好的理由。在 -O3 级别,GCC 应始终生成相同的代码。
int foo( int *a, int n, int x) {
int c, i = 0, j = n;
while (i < n) {
#ifdef PREFETCH
__builtin_prefetch(a+16*i + 15);
#endif /* PREFETCH */
c = (x > a[i]);
j = c ? j : i;
i = 2*i + 1 + c;
}
return j;
}
Run Code Online (Sandbox Code Playgroud)
产生这个
.cfi_startproc
testl %esi, %esi
movl %esi, %eax
jle .L4
xorl %ecx, %ecx
.p2align 4,,10
.p2align 3
.L3:
movslq %ecx, %r8
cmpl %edx, (%rdi,%r8,4)
setl %r8b
cmovge %ecx, %eax
movzbl %r8b, %r8d
leal 1(%r8,%rcx,2), %ecx
cmpl %ecx, %esi
jg .L3
.L4:
rep ret
.cfi_endproc
Run Code Online (Sandbox Code Playgroud)
和这个
.cfi_startproc
testl %esi, %esi
movl %esi, %eax
jle .L5
xorl %ecx, %ecx
.p2align 4,,10
.p2align 3
.L4:
movl %ecx, %r8d
sall $4, %r8d
movslq %r8d, %r8
prefetcht0 60(%rdi,%r8,4)
movslq %ecx, %r8
cmpl %edx, (%rdi,%r8,4)
setl %r8b
testb %r8b, %r8b
movzbl %r8b, %r9d
cmove %ecx, %eax
leal 1(%r9,%rcx,2), %ecx
cmpl %ecx, %esi
jg .L4
.L5:
rep ret
.cfi_endproc
Run Code Online (Sandbox Code Playgroud)