如何让fun1借助restrict关键字生成与fun2相似的汇编指令?
如何让fun1借助restrict生成与fun2相似的优化指令?
我有两个功能完全相同的函数:
#define RES restrict void fun1(double * tmp1, int n) { // 在此函数中,编译器忽略restrict关键字 double * RES a = tmp1; double * RES b = tmp1+n; double * RES c = tmp1+2*n; for (int i =0 ; i < n; ++i) { a[i] += c[i]+b[i]; // 加载a、b、c,存储a a[i] += c[i]*b[i]; // 加载b、c,存储至a } } void fun2(double * RES a, double * RES b, double * RES c, int n) { // fun2(tmp1, tmp1+n, tmp1+2*n,n) // 编译器会考虑restrict关键字 for (int i =0 ; i < n; ++i) { a[i] += c[i]+b[i]; // 加载a、b,两次加载c,存储至a a[i] += c[i]*b[i]; } }
使用gcc-13编译器并添加-O3编译选项时,fun1中编译器会忽略restrict关键字,而fun2中编译器通过该关键字减少了一次加载和存储指令。已知restrict是给编译器的优化提示,请问是否有办法让fun1生成与fun2相似的优化指令?
可行解决方案
1. 用GCC内置函数告知编译器指针无重叠
通过__builtin_assume向编译器传递“a、b、c指向的内存区域无重叠且范围合法”的假设,帮助编译器触发restrict相关优化:
#define RES restrict void fun1(double * tmp1, int n) { double * RES a = tmp1; double * RES b = tmp1+n; double * RES c = tmp1+2*n; // 告知编译器三个指针的内存块独立且无重叠 __builtin_assume(__builtin_object_size(a, 0) == n * sizeof(double)); __builtin_assume(__builtin_object_size(b, 0) == n * sizeof(double)); __builtin_assume(__builtin_object_size(c, 0) == n * sizeof(double)); __builtin_assume(((char*)b - (char*)a) >= n * sizeof(double)); __builtin_assume(((char*)c - (char*)b) >= n * sizeof(double)); for (int i =0 ; i < n; ++i) { a[i] += c[i]+b[i]; a[i] += c[i]*b[i]; } }
__builtin_assume会让编译器认定括号内的表达式恒为真,从而消除内存重叠的顾虑,进行与fun2一致的指令优化。
2. 转换为restrict限定的数组类型
通过typedef定义restrict数组类型,让编译器更清晰地识别指针的内存范围和独立属性:
#define RES restrict void fun1(double * tmp1, int n) { typedef double RES arr_t[]; arr_t *a = (arr_t*)tmp1; arr_t *b = (arr_t*)(tmp1 + n); arr_t *c = (arr_t*)(tmp1 + 2*n); for (int i =0 ; i < n; ++i) { (*a)[i] += (*c)[i] + (*b)[i]; (*a)[i] += (*c)[i] * (*b)[i]; } }
这种方式明确了每个指针对应内存块的restrict属性,编译器会自动应用类似fun2的优化策略。
3. 内部调用已优化的fun2(折中方案)
直接在fun1内部调用fun2,复用其参数层面的restrict优化效果,同时保留fun1的原有接口:
#define RES restrict void fun2(double * RES a, double * RES b, double * RES c, int n) { for (int i =0 ; i < n; ++i) { a[i] += c[i]+b[i]; a[i] += c[i]*b[i]; } } void fun1(double * tmp1, int n) { fun2(tmp1, tmp1+n, tmp1+2*n, n); }
这种方法简单直接,编译器会对fun2进行充分优化,且fun1的调用开销会被-O3下的内联优化消除。
核心原因说明
fun1中编译器忽略restrict的本质是:局部指针的restrict标记无法让编译器确定tmp1+n、tmp1+2*n指向的内存是否与tmp1重叠(比如n为负数的极端情况)。通过上述方法向编译器补充内存范围和无重叠的信息,就能让编译器应用restrict的优化规则,生成与fun2相似的精简指令。
内容的提问来源于stack exchange,提问作者Aditya Kurrodu
相关产品推荐
相关产品推荐

