Я пытаюсь понять, когда и когда не следует использовать ключевое слово 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
во втором выражении, потому что, возможно, b
и c
указывают на то же место. Также необходимо дождаться сохранения b
, прежде чем он сможет загрузить a
по той же причине. Затем он должен дождаться сохранения a
и перезагрузить b
и c
в начале следующего цикла. Если вы вызываете функцию так:
int a[N];
foo(a, a, a, N);
тогда вы сможете понять, почему компилятор должен это делать. Использование restrict
фактически говорит компилятору, что вы никогда этого не сделаете, поэтому он может сбросить избыточную нагрузку c
и загрузить a
до сохранения b
.
В другом посте SO Нильс Пипенбринк приводит рабочий пример этого сценария, демонстрирующий выигрыш в производительности.
Пока я понял, что это хорошая идея - использовать 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;
}