c – __builtin_unreachable有哪些优化方便?
从
gcc的文档来看
我认为__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&,DWORD PTR [rdi] xor eax,eax xor eax,DWORD PTR [rsi] mov DWORD PTR [rsi],eax xor DWORD PTR [rdi],eax ret rswap(int&,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的目的是帮助编译器删除死代码(程序员知道永远不会被执行)并通过让编译器知道路径是“冷”来线性化代码.考虑以下:
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,dil jne .L9 mov edi,8 ret .L9: mov edi,1 call exit_if_true(bool) 注意差异: > xor eax,eax和ret被删除,因为现在编译器知道这是一个死代码. 这里的假设是以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字节对齐的,但事实并非如此.因此使用未对齐的存储和加载,而第二个版本生成更快的指令,需要对齐地址. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |