我试图了解什么时候和什么时候不要在 C 语言中使用 restrict
关键字,以及在什么情况下它会带来切实的好处。
在阅读了“ 解密限制关键字”(它提供了一些关于使用的经验法则)之后,我得到的印象是,当一个函数被传递指针时,它必须考虑所指向的数据可能与传递给该函数的任何其他参数重叠(别名)的可能性。给定一个函数:
foo(int *a, int *b, int *c, int n) {
for (int i = 0; i<n; ++i) {
b[i] = b[i] + c[i];
a[i] = a[i] + b[i] * c[i];
}
}
编译器必须在第二个表达式中重新加载 c
,因为 b
和 c
可能指向同一个位置。出于同样的原因,它还必须等待 b
被存储之后才能加载 a
。然后它必须等待 a
被存储,并且必须在下一个循环的开始重新加载 b
和 c
。如果像这样调用函数:
int a[N];
foo(a, a, a, N);
然后你就知道为什么编译器要这么做了。使用 restrict
有效地告诉编译器,您永远不会这样做,这样它就可以在存储 b
之前减少 c
的冗余负载并加载 a
。
在另一篇 SO 文章中,Nils Pipenbrinck 提供了这个场景的一个工作示例,展示了性能优势。
到目前为止,我已经得出结论,在传递到函数中的指针上使用 restrict
是一个好主意,因为这些指针不会内联。显然,如果代码是内联的,编译器就可以发现指针没有重叠。
现在事情开始变得模糊了
在 Ulrich Drepper 的论文“ 每个程序员都应该知道的关于内存的知识”中,他声明,“除非使用限制,否则所有的指针访问都是别名的潜在来源”,他给出了一个子矩阵乘法的具体代码例子,在这里他使用了 restrict
。
但是,当我使用或不使用 restrict
编译他的示例代码时,在这两种情况下都会得到相同的二进制文件。我用的是 gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu4)
在下面的代码中,我无法弄清楚的是,是否需要重写它以更广泛地使用 restrict
,或者 GCC 中的别名分析是否非常好,以至于它能够发现所有参数都没有相互别名。出于纯粹的教育目的,我如何使用或不使用 restrict
的问题在这个代码-为什么?
为 restrict
编制:
gcc -DCLS=$(getconf LEVEL1_DCACHE_LINESIZE) -DUSE_RESTRICT -Wextra -std=c99 -O3 matrixMul.c -o matrixMul
只需删除 -DUSE_RESTRICT
不使用 restrict
。
#include <stdlib.h>
#include <stdio.h>
#include <emmintrin.h>
#ifdef USE_RESTRICT
#else
#define restrict
#endif
#define N 1000
double _res[N][N] __attribute__ ((aligned (64)));
double _mul1[N][N] __attribute__ ((aligned (64)))
= { [0 ... (N-1)]
= { [0 ... (N-1)] = 1.1f }};
double _mul2[N][N] __attribute__ ((aligned (64)))
= { [0 ... (N-1)]
= { [0 ... (N-1)] = 2.2f }};
#define SM (CLS / sizeof (double))
void mm(double (* restrict res)[N], double (* restrict mul1)[N],
double (* restrict mul2)[N]) __attribute__ ((noinline));
void mm(double (* restrict res)[N], double (* restrict mul1)[N],
double (* restrict mul2)[N])
{
int i, i2, j, j2, k, k2;
double *restrict rres;
double *restrict rmul1;
double *restrict rmul2;
for (i = 0; i < N; i += SM)
for (j = 0; j < N; j += SM)
for (k = 0; k < N; k += SM)
for (i2 = 0, rres = &res[i][j],
rmul1 = &mul1[i][k]; i2 < SM;
++i2, rres += N, rmul1 += N)
for (k2 = 0, rmul2 = &mul2[k][j];
k2 < SM; ++k2, rmul2 += N)
for (j2 = 0; j2 < SM; ++j2)
rres[j2] += rmul1[k2] * rmul2[j2];
}
int main (void)
{
mm(_res, _mul1, _mul2);
return 0;
}