J'essaie de comprendre quand et quand ne pas utiliser l'option restrict
mot-clé en C et dans quelles situations il apporte un avantage tangible.
Après avoir lu, " Démystifier le mot-clé de restriction "(qui fournit quelques règles empiriques sur l'utilisation), j'ai l'impression que lorsqu'une fonction reçoit des pointeurs, elle doit tenir compte de la possibilité que les données pointées puissent se superposer (alias) à tout autre argument passé à la fonction. Étant donné une fonction :
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];
}
}
le compilateur doit recharger c
dans la deuxième expression, parce que peut-être b
y c
pointent vers le même endroit. Il doit également attendre b
pour être stocké avant de pouvoir charger a
pour la même raison. Il doit ensuite attendre a
pour être stocké et doit être rechargé b
y c
au début de la boucle suivante. Si vous appelez la fonction comme ceci :
int a[N];
foo(a, a, a, N);
alors vous pouvez voir pourquoi le compilateur doit faire cela. Utilisation de restrict
indique effectivement au compilateur que vous ne ferez jamais cela, de sorte qu'il peut abandonner la charge redondante de c
et la charge a
avant b
est stocké.
Jusqu'à présent, j'ai compris que c'est une bonne idée d'utiliser restrict
sur les pointeurs que vous passez dans des fonctions qui ne seront pas inlined. Apparemment, si le code est inlined, le compilateur peut comprendre que les pointeurs ne se chevauchent pas.
Maintenant, c'est là que les choses commencent à devenir floues pour moi.
Dans l'article d'Ulrich Drepper, " Ce que tout programmeur devrait savoir sur la mémoire "il affirme que, "à moins que la restriction ne soit utilisée, tous les accès aux pointeurs sont des sources potentielles d'aliasing", et il donne un exemple de code spécifique d'une multiplication de matrices sous-matricielles où il utilise restrict
.
Cependant, lorsque je compile son code d'exemple avec ou sans restrict
J'obtiens des binaires identiques dans les deux cas. J'utilise gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu4)
Ce que je n'arrive pas à comprendre dans le code suivant, c'est s'il doit être réécrit pour faire un usage plus important de l'option restrict
ou si l'analyse des alias dans GCC est si bonne qu'elle est capable de comprendre qu'aucun des arguments n'est un alias. À des fins purement pédagogiques, comment puis-je faire en sorte qu'utiliser ou ne pas utiliser restrict
dans ce code - et pourquoi ?
Pour restrict
compilé avec :
gcc -DCLS=$(getconf LEVEL1_DCACHE_LINESIZE) -DUSE_RESTRICT -Wextra -std=c99 -O3 matrixMul.c -o matrixMul
Il suffit de retirer -DUSE_RESTRICT
de ne pas utiliser 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;
}