__builtin_unreachable可以促进哪些优化?

时间:2019-02-19 10:53:38

标签: c++ gcc optimization

从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

从优化程序的角度来看,我认为stdswaprswap是最佳的。为什么brswap不能编译成相同的东西?我可以使用__builtin_unreachable将其编译为相同的东西吗?

2 个答案:

答案 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, eaxret已被删除,因为现在编译器知道这是无效代码。
  • 编译器交换了分支顺序:现在首先调用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运算符中删除不必要的cmpjnz

编译器应使用编写的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范围是-21474836482147483647(当int是32位类型时),编译器不知道关于实际值范围的信息,并且无法删除默认块(以及一些cmp指令等),但是如果您不将此块添加到switch中,它将警告您。因此,在这种情况下,__builtin_unreachable可能会有所帮助。