Я пытаюсь понять, когда и когда не использовать ключевое слово restrict
в C и в каких ситуациях оно дает ощутимую выгоду.
После прочтения "" Демистификация ограничивающего ключевого слова" (что дает некоторые эмпирические правила использования), создается впечатление, что когда функции передаются указатели, она должна учитывать возможность того, что указанные данные могут перекрываться (псевдоним) с любыми другими аргументами, передаваемыми в функцию. Для функции:
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
эффективно сообщает компилятору, что вы никогда этого не сделаете, чтобы он мог сбросить избыточную нагрузку c
и загрузить a
до того, как будет сохранен b
.
До сих пор я понял, что неплохо использовать restrict
для указателей, которые вы передаете в функции, которые не будут включены. По-видимому, если код встроен, компилятор может понять, что указатели не перекрываются.
Теперь, когда вещи начинают меняться нечеткими.
В статье Ульриха Дреппера " Что каждый программист должен знать о памяти" он делает выражение, что "если не используется ограничение, все ссылки на указатели являются потенциальными источниками псевдонимов", и он дает конкретный пример кода матрицы подматрицы умножить, где он использует 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;
}