关键字

restrict

  1. 当一个指针变量被restrict修饰时
int * restrict ptr;

消除指针别名(No Pointer Aliasing):

  • 在指针 ptr 的生命周期内,它所指向的内存块,只能通过 ptr(或基于 ptr 算术运算得到的指针)来访问。绝对没有第二个指针会同时指向这块内存

指针别名危机(Pointer Aliasing)

void vector_add(int *a, int *b, int *c, int n) {
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
}

普通的 CPU 或 GCC 编译器在编译这个循环时,由于无法确定 c 是否与 a 或 b 存在内存重叠(比如万一调用者传入的是 vector_add(a, b, a+1, n) 呢?),必须强制执行以下安全策略:

  1. 从 a[0] 读数据
  2. 从 b[0] 读数据
  3. 相加,写入 c[0]
  4. 必须重新从内存加载 a[1](因为刚刚写入 c[0] 的操作,有可能意外改写了 a[1] 的值!)

这种由于潜在重叠导致编译器每次写入后都必须重新读取内存的现象,彻底锁死了 DSP 的流水线。

restrict 后的硬件级优化

void vector_add(int * restrict a, int * restrict b, int * restrict c, int n) {
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
}

此时,像 xt-clang 这样的高级 LLVM 优化器会瞬间解除封印,进行以下两项 DSP 核心优化:

🚀 优化 A:激进的自动向量化(Auto-vectorization)
因为知道 c 不可能污染 a 和 b,编译器可以放心大胆地使用 BBE32EP 的 512 位宽向量寄存器。

  • 无 restrict: 挨个点进行标量读、加、写。
  • 有 restrict: 一次性用向量指令 Load 32个 a 元素,Load 32个 b 元素,在向量中批量相加,一次性 Store 32个 c 元素。性能直接暴涨数十倍。

⚡ 优化 B:指令重排与 VLIW 深度流水线(Instruction Scheduling)
在 BBE32EP 这样的超长指令字(VLIW)架构中,硬件可以在同一个周期执行(读+算+写)。

  • 有了 restrict,编译器可以将多轮循环的读操作提前,让内存读取流水线(Load Pipeline)完全跑满,实现软件流水线化(Software Pipelining),消除任何等待数据的空转周期(Stall)。

__builtin_expect(EXP, EXPETCED_VALUE)

#define NASSERT(x) {(void)__builtin_expect((x)!=0,1); ASSERT(x); }
  • 告诉编译器:“预测 (x) != 0 这个表达式的结果,有极高的概率是 1(即真 True)。”
  • 前面的 (void): 纯粹是为了丢弃该内建函数的返回值,防止编译器报 “unused value(未使用的返回值)” 的警告。