74 votes

Règles d'utilisation du mot-clé restrict en C ?

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é.

Dans un autre article de SO, Nils Pipenbrinck, fournit un exemple de ce scénario démontrant l'avantage en termes de performance.

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

16voto

Hans Passant Points 475940

C'est un indice pour l'optimiseur de code. L'utilisation de restrict lui garantit qu'il peut stocker une variable de type pointeur dans un registre du CPU et qu'il n'a pas besoin d'envoyer une mise à jour de la valeur du pointeur en mémoire pour qu'un alias soit également mis à jour.

Le fait d'en tirer profit ou non dépend fortement des détails d'implémentation de l'optimiseur et du CPU. Les optimiseurs de code sont déjà fortement investis dans la détection du non-aliasing car il s'agit d'une optimisation très importante. Ils ne devraient avoir aucun problème à le détecter dans votre code.

15voto

De plus, GCC 4.0.0-4.4 a un bogue de régression qui fait que le mot-clé restrict est ignoré. Ce bogue a été signalé comme corrigé dans la version 4.5 (j'ai perdu le numéro du bogue cependant).

3voto

Artyom Shalkhakov Points 659

(Je ne sais pas si l'utilisation de ce mot-clé vous donne un avantage significatif, en fait. Il est très facile pour le programmeur de se tromper avec ce qualificatif car il n'y a pas d'application, donc un optimiseur ne peut pas être certain que le programmeur ne "ment" pas).

Lorsque vous savez qu'un pointeur A est le seul pointeur vers une certaine région de la mémoire, c'est-à-dire qu'il n'a pas d'alias (c'est-à-dire que tout autre pointeur B sera nécessairement inégal à A, B != A), vous pouvez indiquer ce fait à l'optimiseur en qualifiant le type de A avec le mot-clé "restrict".

J'ai écrit à ce sujet ici : http://mathdev.org/node/23 et j'ai essayé de montrer que certains pointeurs restreints sont en fait "linéaires" (comme mentionné dans ce post).

3voto

marko Points 5063

Il est intéressant de noter que les versions récentes de clang sont capables de générer du code avec une vérification de l'aliasing au moment de l'exécution, et deux chemins de code : l'un pour les cas où il y a un aliasing potentiel et l'autre pour les cas où il est évident qu'il n'y a aucun risque.

Cela dépend clairement du fait que les extents des données pointées soient visibles pour le compilateur - comme ils le seraient dans l'exemple ci-dessus.

Je pense que la principale justification est pour les programmes faisant un usage intensif de STL - et particulièrement de <algorithm> où il est soit difficile, soit impossible d'introduire la __restrict qualificatif.

Bien sûr, tout cela se fait au détriment de la taille du code, mais élimine une grande partie du potentiel de bogues obscurs qui pourraient résulter pour les pointeurs déclarés en tant que __restrict n'étant pas tout à fait aussi peu chevauchante que le développeur le pensait.

Je serais surpris si GCC n'avait pas également obtenu cette optimisation.

1voto

Logan Capaldo Points 22145

S'il y a une différence, le déménagement mm vers un DSO séparé (de telle sorte que gcc ne puisse plus tout savoir sur le code appelant) sera le moyen de le démontrer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X