c++ - __builtin_unreachable 促进了哪些优化?

标签 c++ gcc optimization

从gcc的文档来看

If control flow reaches the point of the __builtin_unreachable, the program is undefined.

我认为 __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 让它编译成同样的东西吗?

最佳答案

__builtin_unreachable 的目的是帮助编译器:

  • 删除死代码(程序员知道永远不会执行的代码)
  • 通过让编译器知道路径是“冷的”来线性化代码(通过调用 noreturn 函数可以达到类似的效果)

考虑以下几点:

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
        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              ; changed jump
        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 调用或exception throw 两者都很少见,在优化​​过程中不需要优先考虑。

您正试图将它用于不同的目的 - 通过向编译器提供有关别名的信息(并且您可以尝试对对齐做同样的事情)。不幸的是,GCC 不理解这种地址检查。

正如您所注意到的,添加 __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 字节对齐的,但事实并非如此。因此使用未对齐的存储和加载,而第二个版本生成需要对齐地址的更快的指令。

关于c++ - __builtin_unreachable 促进了哪些优化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54764535/

相关文章:

C++:为什么在另一个函数中允许函数声明而不是函数定义?

gcc - eclipse cdt 无法解析 <stdint.h>

c# - 简单的 MVC 评论审核

c++ - NDK '^' token 之前的预期不合格 ID

c++ - 无法在 boost-python 中导入 numpy

找不到自定义头文件

c - 使用内联组件绘制像素 VGA

javascript - 卸载,还是隐藏在 React 中?

c++ - 什么是复制省略和返回值优化?

c++ - std::stack::push 和 std::stack:pop 线程安全吗?