从gcc文档中判断
如果控制流到达
__builtin_unreachable
的点,则程序未定义。
我认为__builtin_unreachable
可以以各种创造性的方式用作优化器的提示。所以我做了一个小实验
void stdswap(int& x, int& y)
{
std::swap(x, y);
}
void brswap(int& x, int& y)
{
if(&x == &y)
__builtin_unreachable();
x ^= y;
y ^= x;
x ^= y;
}
void rswap(int& __restrict x, int& __restrict y)
{
x ^= y;
y ^= x;
x ^= y;
}
gets compiled to(g ++ -O2)
stdswap(int&, int&):
mov eax, DWORD PTR [rdi]
mov edx, DWORD PTR [rsi]
mov DWORD PTR [rdi], edx
mov DWORD PTR [rsi], eax
ret
brswap(int&, int&):
mov eax, DWORD PTR [rdi]
xor eax, DWORD PTR [rsi]
mov DWORD PTR [rdi], eax
xor eax, DWORD PTR [rsi]
mov DWORD PTR [rsi], eax
xor DWORD PTR [rdi], eax
ret
rswap(int&, int&):
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
mov DWORD PTR [rdi], eax
mov DWORD PTR [rsi], edx
ret
从优化程序的角度来看,我认为stdswap
和rswap
是最佳的。为什么brswap
不能编译成相同的东西?我可以使用__builtin_unreachable
将其编译为相同的东西吗?
答案 0 :(得分:5)
__builtin_unreachable
的目的是通过让编译器知道路径为“冷”来帮助编译器删除无效代码(程序员知道永远不会执行)并线性化代码。请考虑以下内容:
void exit_if_true(bool x);
int foo1(bool x)
{
if (x) {
exit_if_true(true);
//__builtin_unreachable(); // we do not enable it here
} else {
std::puts("reachable");
}
return 0;
}
int foo2(bool x)
{
if (x) {
exit_if_true(true);
__builtin_unreachable(); // now compiler knows exit_if_true
// will not return as we are passing true to it
} else {
std::puts("reachable");
}
return 0;
}
生成的代码:
foo1(bool):
sub rsp, 8
test dil, dil
je .L2 ; that jump is going to change as branches will be swapped
mov edi, 1
call exit_if_true(bool)
xor eax, eax ; that tail is going to be removed
add rsp, 8
ret
.L2:
mov edi, OFFSET FLAT:.LC0
call puts
xor eax, eax
add rsp, 8
ret
foo2(bool):
sub rsp, 8
test dil, dil
jne .L9
mov edi, OFFSET FLAT:.LC0
call puts
xor eax, eax
add rsp, 8
ret
.L9:
mov edi, 1
call exit_if_true(bool)
注意差异:
xor eax, eax
和ret
已被删除,因为现在编译器知道这是无效代码。puts
进行分支,以便条件跳转可以更快(即使被预测,未采用的分支也会更快)。这里的假设是,以noreturn
函数调用或__builtin_unreachable
结尾的分支将仅执行一次,或者导致longjmp
调用或异常抛出,这两种情况很少见并且会发生在优化过程中无需确定优先级。
您正在尝试将其用于其他目的-通过提供有关别名的编译器信息(并且您可以尝试对对齐进行相同操作)。不幸的是,海湾合作委员会不了解这种地址检查。
您已经注意到,添加__restrict__
会有所帮助。因此,__restrict__
适用于别名,__builtin_unreachable
无效。
请看以下使用__builtin_assume_aligned
的示例:
void copy1(int *__restrict__ dst, const int *__restrict__ src)
{
if (reinterpret_cast<uintptr_t>(dst) % 16 == 0) __builtin_unreachable();
if (reinterpret_cast<uintptr_t>(src) % 16 == 0) __builtin_unreachable();
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
void copy2(int *__restrict__ dst, const int *__restrict__ src)
{
dst = static_cast<int *>(__builtin_assume_aligned(dst, 16));
src = static_cast<const int *>(__builtin_assume_aligned(src, 16));
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
生成的代码:
copy1(int*, int const*):
movdqu xmm0, XMMWORD PTR [rsi]
movups XMMWORD PTR [rdi], xmm0
ret
copy2(int*, int const*):
movdqa xmm0, XMMWORD PTR [rsi]
movaps XMMWORD PTR [rdi], xmm0
ret
您可以假设编译器可以理解dst % 16 == 0
表示指针是16字节对齐的,但不是。因此,使用未对齐的存储和加载,而第二个版本将生成更快的指令,这些指令需要地址进行对齐。
答案 1 :(得分:1)
我认为您是在尝试错误地朝错误的方向微优化代码。
__ builtin_unreachable以及__builtin_expect会执行预期的操作-在您的情况下,请从未使用的if运算符中删除不必要的cmp
和jnz
。
编译器应使用编写的C代码生成机器代码,以生成可预测的程序。并且在优化过程中,它可以在优化算法知道的情况下找到并优化(即用更好的机器代码版本替换)某些模式-这样的优化不会破坏程序的行为。
例如像
char a[100];
for(int i=0; i < 100; i++)
a[i] = 0;
将替换对库std :: memset(a,0,100)的单次调用,该调用是使用汇编实现的,并且对于当前的CPU体系结构是最佳的。
以及能够检测到的编译器
x ^= y;
y ^= x;
x ^= y;
,并用最简单的mashie代码替换。
我认为您的if运算符和未到达的指令影响了编译器优化器,因此无法进行优化。
在交换两个整数的情况下,编译器可以自行删除第3个临时交换变量,即类似
movl $2, %ebx
movl $1, %eax
xchg %eax,%ebx
其中ebx和eax寄存器值实际上是您的x和y。您可以像自己一样实现它
void swap_x86(int& x, int& y)
{
__asm__ __volatile__( "xchg %%rax, %%rbx": "=a"(x), "=b"(y) : "a"(x), "b"(y) : );
}
...
int a = 1;
int b = 2;
swap_x86(a,b);
何时使用__builtin_unreachable?可能是当您知道某种情况实际上是不可能的,但是从逻辑上讲可能会发生。即您有
之类的功能void foo(int v) {
switch( v ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
default:
__builtin_unreachable();
}
}
您知道v
参数值始终在0到3之间。但是,int范围是-2147483648
到2147483647
(当int是32位类型时),编译器不知道关于实际值范围的信息,并且无法删除默认块(以及一些cmp指令等),但是如果您不将此块添加到switch中,它将警告您。因此,在这种情况下,__builtin_unreachable
可能会有所帮助。