web-dev-qa-db-fra.com

Que signifie le mot clé restrict en C ++?

J'étais toujours incertain, que signifie le mot clé restrict en C++?

Cela signifie-t-il que les deux pointeurs ou plus donnés à la fonction ne se chevauchent pas? Qu'est-ce que cela signifie?

177
user34537

Dans son article, Memory Optimization , Christer Ericson déclare que, bien que restrict ne fasse pas encore partie de la norme C++, il est pris en charge par de nombreux compilateurs et il recommande son utilisation lorsque celle-ci est disponible:

mot clé restreint

! Nouveauté 1999 ANSI/ISO C

! Pas encore en standard C++, mais supporté par de nombreux compilateurs C++

! Un indice seulement, alors ne faites rien et restez conforme

Un pointeur qualifié (ou une référence) ...

! ... est fondamentalement une promesse faite au compilateur que, pour l'étendue du pointeur, la cible du pointeur ne sera accessible que par ce pointeur (et les pointeurs copiés à partir de celui-ci).

Dans les compilateurs C++ qui le supportent, il devrait probablement se comporter comme dans C.

Voir ceci SO post pour plus de détails: tilisation réaliste du mot clé ‘restrict’ ​​de C99??

Prenez une demi-heure pour parcourir le papier d'Ericson, c'est intéressant et vaut la peine.

Modifier

J'ai également constaté que le compilateur d'IBM AIX C/C++ prend en charge le __restrict__ mot clé .

g ++ semble également supporter cela car le programme suivant compile proprement sur g ++:

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) {
    return *a + *b;
}

int main(void) {
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;
}

J'ai aussi trouvé un bel article sur l'utilisation de restrict:

démystifier le mot clé Restrict

Edit2

J'ai rencontré un article qui traite spécifiquement de l'utilisation de restrict dans les programmes C++:

Load-hit-stores et le mot clé __restrict

Microsoft Visual C++ prend également en charge le __restrict mot clé .

136
Robert S. Barnes

Comme d’autres l’ont dit, si signifie rien à partir de C++ 14 , considérons donc le __restrict__ Extension GCC qui fait la même chose que C99 restrict.

C99

restrict indique que deux pointeurs ne peuvent pas pointer sur des régions de mémoire qui se chevauchent. L'utilisation la plus courante est pour les arguments de fonction.

Cela restreint le mode d’appel de la fonction, mais permet davantage d’optimisations de compilation.

Si l'appelant ne suit pas le contrat restrict, comportement non défini.

Le brouillon C99 N1256 6.7.3/7 "Qualificatifs de type" indique:

L’utilisation prévue du qualificatif restrict (comme la classe de stockage de registre) est de promouvoir l’optimisation. La suppression de toutes les occurrences du qualificatif de toutes les unités de traduction en cours de prétraitement composant un programme conforme ne change pas sa signification (comportement observable, par exemple).

et 6.7.3.1 "Définition formelle de restreindre" donne les détails sanglants.

Une optimisation possible

Le exemple Wikipedia est très éclairant.

Il montre clairement comment, en tant que , il permet de sauvegarder une instruction d'assemblage .

Sans restreindre:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

Pseudo Assemblée:

load R1 ← *x    ; Load the value of x pointer
load R2 ← *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2 → *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because a may be equal to x.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

Avec restreindre:

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x);

Pseudo Assemblée:

load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

Est-ce que GCC le fait vraiment?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

Avec -O0, ce sont les mêmes.

Avec -O3:

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

Pour les non-initiés, le convention d'appel est:

  • rdi = premier paramètre
  • rsi = second paramètre
  • rdx = troisième paramètre

La sortie de GCC était encore plus claire que l’article du wiki: 4 instructions vs 3 instructions.

Tableaux

Jusqu'à présent, nous avons des économies d'instruction simples, mais si le pointeur représente les tableaux à boucler, cas d'utilisation courant, un ensemble d'instructions pourrait être sauvegardé, comme mentionné par supercat et michael .

Considérons par exemple:

void f(char *restrict p1, char *restrict p2, size_t size) {
     for (size_t i = 0; i < size; i++) {
         p1[i] = 4;
         p2[i] = 9;
     }
 }

En raison de restrict, un compilateur intelligent (ou humain) pourrait optimiser cela pour:

memset(p1, 4, size);
memset(p2, 9, size);

Ce qui est potentiellement beaucoup plus efficace car il peut être optimisé par Assembly sur une implémentation décente de libc (comme glibc) Est-il préférable d’utiliser std :: memcpy () ou std :: copy () en termes de performances? =, éventuellement avec instructions SIMD .

Sans, restreindre, cette optimisation ne pourrait pas être effectuée, par exemple. considérer:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

Alors for version fait:

p1 == {4, 4, 4, 9}

tandis que la version memset fait:

p1 == {4, 9, 9, 9}

Est-ce que GCC le fait vraiment?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

Avec -O0, les deux sont identiques.

Avec -O3:

  • avec restreindre:

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      Push   %rbp
    3f6:   53                      Push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq
    

    Deux appels memset comme prévu.

  • sans restriction: pas d'appels stdlib, juste une itération de 16 déroulement de la boucle que je n'ai pas l'intention de reproduire ici :-)

Je n'ai pas eu la patience de les comparer, mais je pense que la version restreinte sera plus rapide.

Règle de pseudonyme stricte

Le mot clé restrict n'affecte que les pointeurs de types compatibles (par exemple, deux int*) parce que les règles strictes sur le crénelage indiquent que le crénelage des types incompatibles est un comportement indéfini par défaut, de sorte que les compilateurs peuvent supposer que cela ne se produit pas et l’optimiser.

Voir: Quelle est la règle de crénelage strict?

Est-ce que ça marche pour les références?

Selon la documentation de GCC, il fait: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html avec la syntaxe:

int &__restrict__ rref

Il existe même une version pour this des fonctions membres:

void T::fn () __restrict__

Rien. Il a été ajouté à la norme C99.

20
dirkgently

This est la proposition originale pour ajouter ce mot clé. Comme Dirkgently l'a fait remarquer, il s'agit d'une fonction C99 ; cela n'a rien à voir avec C++.

10
unwind

Ce mot clé n'existe pas en C++. La liste des mots-clés C++ se trouve dans la section 2.11/1 du standard de langage C++. restrict est un mot clé dans la version C99 du langage C et non dans C++.

5
AnT

Etant donné que les fichiers d’en-tête de certaines bibliothèques C utilisent le mot-clé, le langage C++ devra faire quelque chose à ce sujet .. au minimum, en ignorant le mot-clé, nous n’avons donc pas à # définir le mot-clé en une macro vierge pour le supprimer. .

4
Johan Boulé