Próbuję zrozumieć, kiedy i kiedy nie używać słowa kluczowego restrict
w C i w jakich sytuacjach zapewnia on wymierną korzyść.Zasady korzystania z słowa kluczowego restrict w języku C?
Po przeczytaniu "Demystifying The Restrict Keyword" (co zapewnia pewne zasady dotyczące użycia), mam wrażenie, że gdy funkcja jest przekazywana wskaźniki, musi uwzględnić możliwość, że wskazane dane mogą się nakładać (alias) z dowolnymi innymi argumentami przekazywanymi do funkcji. Biorąc pod uwagę funkcję:
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];
}
}
kompilator musi przeładować c
w drugiej wypowiedzi, bo może b
i c
punktu do tej samej lokalizacji. Musi również czekać, aż b
zostanie zapisany, zanim będzie mógł załadować a
z tego samego powodu. Musi wówczas czekać na zapisanie a
i musi ponownie załadować b
i c
na początku następnej pętli. Jeśli wywołasz funkcję podobną do tej:
int a[N];
foo(a, a, a, N);
to możesz zobaczyć, dlaczego kompilator musi to zrobić. Korzystanie z restrict
skutecznie informuje kompilator, że nigdy tego nie zrobisz, dzięki czemu może zrzucić zbędne obciążenie z c
i załadować a
, zanim zostanie zapisane .
Tak dalece zostały zebrane, że jest to dobry pomysł, aby użyć restrict
na wskaźniki przekazać do funkcji, które nie zostanie włączonych. Wygląda na to, że jeśli kod jest wstawiony, kompilator może zorientować się, że wskaźniki się nie pokrywają.
Teraz zaczynają się robić dla mnie niewyraźne rzeczy.
W artykule Ulricha Drepper za „What every programmer should know about memory” On sprawia, że stwierdzenie, że „o ile nie ograniczają stosuje się wszystkie dostępy wskaźnika są potencjalnymi źródłami aliasing”, a on daje konkretny przykład kodu matrycy podmatrycy pomnożyć gdzie używa restrict
.
Jednak, gdy skompiluję jego przykładowy kod z lub bez restrict
, otrzymuję identyczne binarki w obu przypadkach. Używam gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu4)
Rzecz nie mogę dowiedzieć się, w poniższym kodzie jest, czy to musi być zapisane do szerszego stosowania restrict
, lub jeśli analiza alias w GCC jest tak dobre, że jest w stanie aby dowiedzieć się, że żaden z argumentów nie jest aliasami nawzajem. Dla celów czysto edukacyjnych, w jaki sposób mogę dokonać użycia lub nie używając sprawy restrict
w tym kodzie - i dlaczego?
Dla restrict
skompilowany z:
gcc -DCLS=$(getconf LEVEL1_DCACHE_LINESIZE) -DUSE_RESTRICT -Wextra -std=c99 -O3 matrixMul.c -o matrixMul
Wystarczy usunąć -DUSE_RESTRICT
nie używać 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;
}
Szybka odpowiedź: ** Nie rób **. Używanie kolejnego kwalifikatora typu sprawia, że kod jest mniej czytelny i zwiększa szansę na trudne do debugowania błędy. W większości przypadków powinieneś ufać kompilatorowi, aby wymyślić to. –
Ale jeśli piszesz bibliotekę, kompilator * nie może tego * wymyślić, ponieważ nie może znać wszystkich dzwoniących. Również użycie 'restrict' dla parametru funkcji służy jako dokumentacja dla użytkownika API. – JaakkoK
Co stanie się, jeśli zmienisz połączenie w 'main' na' mm (_res, _mul1, _mul1)? Wiem, że jest to niezdefiniowane, gdy masz 'restrict', ale może to stanowić różnicę. Możesz również podzielić 'mm' na swój własny plik i skompilować go osobno. – JaakkoK