在C中使用restrict关键字的规则?

我试图了解何时以及何时不在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 ,因为可能bc指向相同的位置。 它也必须等待b存储才可以加载a出于同样的原因。 然后它必须等待a被存储并且必须在下一个循环开始时重新加载bc 。 如果你像这样调用函数:

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;
}

此外,GCC 4.0.0-4.4有一个回归bug,导致restrict关键字被忽略。 这个bug在4.5版中报告为固定的(尽管我错过了错误编号)。


这是代码优化器的一个暗示。 使用restrict可以确保它可以将指针变量存储在CPU寄存器中,而不必将指针值的更新刷新到内存中,以便更新别名。

它是否利用它很大程度上取决于优化器和CPU的实现细节。 由于代码优化器是非常重要的优化,因此代码优化器已经在检测非混叠方面投入了大量资金。 在代码中检测它应该没有问题。


(我不知道使用这个关键字实际上是否给你一个很大的优势,程序员很容易犯这个限定符,因为没有强制执行,所以优化器不能确定程序员不会“说谎”。 )

当你知道指针A是唯一指向某个内存区域的指针时,也就是说,它没有别名(也就是说,任何其他指针B必然不等于A,B!= A),你可以告诉通过使用“restrict”关键字限定A的类型来优化器这一事实。

我在这里写了这个:http://mathdev.org/node/23并试图表明一些限制指针实际上是“线性的”(正如那篇文章中提到的那样)。

链接地址: http://www.djcxy.com/p/64043.html

上一篇: Rules for using the restrict keyword in C?

下一篇: Inline functions in C#?