如何优化c语言中使用__builtin_unreachable的代码?

2026-04-16 18:282阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计857个文字,预计阅读时间需要4分钟。

如何优化c语言中使用__builtin_unreachable的代码?

从gcc的文档中可以看出,如果控制流到达了`__builtin_unreachable()`的点,程序的行为是未定义的。我认为`__builtin_unreachable()`可以用来作为各种创造性的优化方式的提示。因此,我进行了一个小实验:`void stdswap()`。

从 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

我假设从优化器的角度来看,stdswap和rswap是最佳的.为什么不将brswap编译成同一个东西?我能用__builtin_unreachable将它编译成同样的东西吗?

__builtin_unreachable的目的是帮助编译器删除死代码(程序员知道永远不会被执行)并通过让编译器知道路径是“冷”来线性化代码.考虑以下:

如何优化c语言中使用__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调用或异常抛出,这两种情况都很少见,并且在优化期间不需要优先处理.

您正在尝试将其用于不同的目的 – 通过提供有关别名的编译器信息(您可以尝试对齐进行相同操作).不幸的是,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字节对齐的,但事实并非如此.因此使用未对齐的存储和加载,而第二个版本生成更快的指令,需要对齐地址.

本文共计857个文字,预计阅读时间需要4分钟。

如何优化c语言中使用__builtin_unreachable的代码?

从gcc的文档中可以看出,如果控制流到达了`__builtin_unreachable()`的点,程序的行为是未定义的。我认为`__builtin_unreachable()`可以用来作为各种创造性的优化方式的提示。因此,我进行了一个小实验:`void stdswap()`。

从 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

我假设从优化器的角度来看,stdswap和rswap是最佳的.为什么不将brswap编译成同一个东西?我能用__builtin_unreachable将它编译成同样的东西吗?

__builtin_unreachable的目的是帮助编译器删除死代码(程序员知道永远不会被执行)并通过让编译器知道路径是“冷”来线性化代码.考虑以下:

如何优化c语言中使用__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调用或异常抛出,这两种情况都很少见,并且在优化期间不需要优先处理.

您正在尝试将其用于不同的目的 – 通过提供有关别名的编译器信息(您可以尝试对齐进行相同操作).不幸的是,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字节对齐的,但事实并非如此.因此使用未对齐的存储和加载,而第二个版本生成更快的指令,需要对齐地址.