GCC拓展大冒险
免责声明:请不要试图在 正式比赛 中使用这里提到的 任何技巧。笔者为此不负任何责任。
1. 在标准环境下使用 c++17 库函数
标准环境指 gcc9.3, -std=c++14。注意到 c++ 有个 experimental 库。
#include <experimental/numeric>
#define gcd std::experimental::fundamentals_v2::gcd
然后我们就有 c++ 自带的 binary gcd 了!
同理:
#include <experimental/memory_resource>
#include <experimental/list>
template <typename T>
using pmr_list = std::experimental::fundamentals_v2::list<T>;这样我们有了一个可以外接内存池的 STL 链表。
2. 编译器内置指令集
using vec = __attribute__((vector_size(32))) int;这行代码定义了一个由 32 个 byte 打包成的 int 类型向量,其加、减等运算与 __m256i 完全等价,只不过不需要背冗长的指令集函数了!
如果真的需要,可以采用一下技巧将其零成本转为 __m256i:
union my_m256 {
vec i32;
__m256i mm;
};调用 __m256i 相关函数时,传入 foo.mm 即可。
3. 内存对齐
struct __attribute__((packed)) Foo {
char a;
int b;
char c;
};
// sizeof(Foo) == 6#pragma pack(1) 也能起到同样的效果。
4. 强制内联
编译器在大多数时候都能准确地判断出一个函数是否需要内联;但偶尔也会有犯错的时候,这时我们可以强制其内联某个函数。
我们当然知道可以采用宏定义,但是这太危险了。
__attribute__((always_inline)) inline int add(int a, int b) { return a + b; }
[[gnu::always_inline]] inline int minus(int a, int b) { return a - b; }注意不是所有函数都能够强制内联。
5. 新鲜出炉的 __builtins
__builtin_{popcount,clz,ctz}g用法:同原函数,但是参数传一个无符号整形,编译器自动选择应该生成多少位的指令,不用写 ll 等后缀了。
此外,clzg, ctzg 还可以传第二个参数,表示第一个参数为 $0$ 的时候应该返回多少。
gcc 14 开始支持。
__builtin_bitreverse正在实现,详见 bugzilla。