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

186

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

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

Tapis
la source
26
restrictest un mot-clé c99. Oui, Rpbert S. Barnes, je sais que la plupart des compilateurs prennent en charge __restrict__. Vous noterez que tout ce qui a des doubles traits de soulignement est, par définition, spécifique à l'implémentation et donc PAS C ++ , mais une version spécifique au compilateur de celui-ci.
KitsuneYMG
6
Quelle? Ce n'est pas parce que c'est spécifique à l'implémentation qu'il n'est pas C ++; le C ++ autorise explicitement des éléments spécifiques à l'implémentation, et ne les interdit pas ou ne les rend pas en C ++.
Alice
6
@Alice KitsuneYMG signifie qu'il ne fait pas partie de l'ISO C ++ et est plutôt considéré comme une extension C ++. Les créateurs de compilateurs sont autorisés à créer et à distribuer leurs propres extensions, qui coexistent avec ISO C ++ et agissent dans le cadre d'un ajout non officiel au C ++ généralement moins ou non portable. Des exemples seraient l'ancien C ++ managé de MS et leur C ++ / CLI plus récent. D'autres exemples seraient les directives de préprocesseur et les macros fournies par certains compilateurs, comme la #warningdirective commune , ou les macros de signature de fonction ( __PRETTY_FUNCTION__sur GCC, __FUNCSIG__sur MSVC, etc.).
Justin Time - Réintègre Monica
5
@Alice À ma connaissance, C ++ 11 n'impose pas une prise en charge complète de tout C99, pas plus que C ++ 14 ou ce que je sais de C ++ 17. restrictn'est pas considéré comme un mot clé C ++ (voir en.cppreference.com/w/cpp/keyword ), et en fait, la seule mention de restrictdans la norme C ++ 11 (voir open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , une copie du FDIS avec des modifications rédactionnelles mineures, §17.2 [library.c], PDF page 413) déclare que:
Justin Time - Réintégrer Monica
5
@Alice Comment ça? J'ai indiqué la partie qui dit que restrictdoit être omis (exclu de, laissé de côté) les signatures et la sémantique des fonctions de la bibliothèque standard C lorsque ces fonctions sont incluses dans la bibliothèque standard C ++. Ou en d'autres termes, j'ai déclaré le fait que si la signature d'une fonction de bibliothèque standard C contient restricten C, le restrictmot - clé doit être supprimé de la signature de l'équivalent C ++.
Justin Time - Réintègre Monica

Réponses:

143

Dans son article, Memory Optimization , Christer Ericson dit que bien que restrictne fasse pas encore partie de la norme C ++, il est pris en charge par de nombreux compilateurs et il recommande son utilisation lorsqu'elle est disponible:

restreindre le mot-clé

! Nouveau à la norme 1999 ANSI / ISO C

! Pas encore dans le standard C ++, mais pris en charge par de nombreux compilateurs C ++

! Un indice seulement, donc peut ne rien faire et être toujours conforme

Un pointeur (ou référence) qualifié de restriction ...

! ... est fondamentalement une promesse au compilateur que pour la portée 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 prennent en charge, il devrait probablement se comporter de la même manière qu'en C.

Voir cet article SO pour plus de détails: Utilisation réaliste du mot-clé C99 'restrict'?

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

Éditer

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

g ++ semble également prendre en charge cela car le programme suivant se 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 également trouvé un bel article sur l'utilisation de restrict:

Démystifier le mot-clé Restreindre

Modifier2

Je suis tombé sur un article qui traite spécifiquement de l'utilisation de restrict dans les programmes C ++:

Load-hit-stores et le mot clé __restrict

En outre, Microsoft Visual C ++ prend également en charge le __restrictmot clé .

Robert S. Barnes
la source
2
Le lien papier sur l'optimisation de la mémoire est mort, voici un lien vers l'audio de sa présentation GDC. gdcvault.com/play/1022689/Memory
Grimeh
1
@EnnMichael: Évidemment, si vous comptez l'utiliser dans un projet C ++ portable, vous devriez le faire #ifndef __GNUC__ #define __restrict__ /* no-op */ou similaire. Et définissez-le comme __restrictsi _MSC_VERest défini.
Peter Cordes
102

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

C99

restrictdit que deux pointeurs ne peuvent pas pointer vers des régions de mémoire qui se chevauchent. L'utilisation la plus courante concerne les arguments de fonction.

Cela restreint la façon dont la fonction peut être appelée, mais permet davantage d'optimisations de compilation.

Si l'appelant ne respecte pas le restrictcontrat, comportement indéfini.

Le projet de C99 N1256 6.7.3 / 7 "Qualificatifs de type" dit:

L'utilisation prévue du qualificatif restrict (comme la classe de stockage de registre) est de promouvoir l'optimisation, et la suppression de toutes les instances du qualificatif de toutes les unités de traduction de prétraitement composant un programme conforme ne change pas sa signification (c'est-à-dire le comportement observable).

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

Une optimisation possible

L' exemple de Wikipedia est très éclairant.

Il montre clairement comment car il permet de sauvegarder une instruction d'assemblage .

Sans restriction:

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

Pseudo assemblage:

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 x may point to a (a aliased by x) thus 
; the value of x will change when the value of a
; changes.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

Avec restrict:

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

Pseudo assemblage:

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" is no longer needed.
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, la convention d'appel est:

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

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

Tableaux

Jusqu'à présent, nous avons des économies d'instructions uniques, mais si le pointeur représente des tableaux à boucler, un cas d'utilisation courant, alors un tas d'instructions pourrait être enregistré, comme mentionné par supercat et michael .

Considérez 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é pour l'assemblage sur une implémentation libc décente (comme la glibc) Est-il préférable d'utiliser std :: memcpy () ou std :: copy () en termes de performances? , éventuellement avec des instructions SIMD .

Sans, restreignez, cette optimisation ne pourrait pas être faite, par exemple, considérez:

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

Ensuite, la forversion fait:

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

tandis que la memsetversion 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 memsetappels comme prévu.

  • sans restriction: pas d'appels stdlib, juste une boucle large de 16 itérations 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 restrictive sera plus rapide.

Règle d'aliasing stricte

Le restrictmot-clé n'affecte que les pointeurs de types compatibles (par exemple deux int*) car les règles strictes d'aliasing indiquent que l'aliasing des types incompatibles est un comportement indéfini par défaut, et les compilateurs peuvent donc supposer que cela ne se produit pas et s'optimiser.

Voir: Quelle est la règle stricte d'aliasing?

Cela fonctionne-t-il pour les références?

Selon la documentation du GCC, il le 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 thisles fonctions membres:

void T::fn () __restrict__
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
belle réponse. Que faire si l'alias strict est désactivé -fno-strict-aliasing, alors restrictne devrait faire aucune différence entre les pointeurs du même type ou de types différents, non? (Je fais référence à "Le mot clé restrict n'affecte que les pointeurs de types compatibles")
idclev 463035818
@ tobi303 Je ne sais pas! Faites-moi savoir si vous le savez avec certitude ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@jww oui, c'est une meilleure façon de le formuler. Mis à jour.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
restrictsignifie quelque chose en C ++. Si vous appelez une fonction de bibliothèque C avec des restrictparamètres d'un programme C ++, vous devez obéir aux implications de cela. Fondamentalement, si restrictest utilisé dans une API de bibliothèque C, cela signifie quelque chose pour quiconque l'appelle depuis n'importe quel langage, y compris le FFI dynamique de Lisp.
Kaz le
22

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

dirkgently
la source
8
Ce n'est pas tout à fait vrai. Apparemment, il est pris en charge par certains compilateurs C ++ et certaines personnes recommandent fortement son utilisation lorsqu'elle est disponible, voir ma réponse ci-dessous.
Robert S.Barnes
19
@Robert S Barnes: Le standard C ++ ne reconnaît pas restrictcomme mot clé. Ma réponse est donc correcte. Ce que vous décrivez est un comportement spécifique à l'implémentation et quelque chose sur lequel vous ne devriez pas vraiment vous fier.
dirkgently
27
@dirkgently: Avec tout le respect que je vous dois, pourquoi pas? De nombreux projets sont liés à des extensions de langage non standard spécifiques prises en charge par des compilateurs spécifiques ou très peu nombreux. Le noyau Linux et gcc viennent à l'esprit. Il n'est pas rare de s'en tenir à un compilateur spécifique, ou même à une révision spécifique d'un compilateur spécifique pendant toute la durée de vie utile d'un projet. Tous les programmes n'ont pas besoin d'être strictement conformes.
Robert S.Barnes
7
@Rpbert S. Barnes: Je ne peux pas insister davantage sur les raisons pour lesquelles vous ne devriez pas dépendre du comportement spécifique à l'implémentation. Quant à Linux et gcc - réfléchissez et vous verrez pourquoi ils ne sont pas un bon exemple pour votre défense. Je n'ai pas encore vu un logiciel à succès modéré fonctionner sur une seule version de compilateur pendant sa durée de vie.
dirkgently
16
@Rpbert S. Barnes: La question disait c ++. Pas MSVC, pas gcc, pas AIX. Si acidzombie24 voulait des extensions spécifiques au compilateur, il aurait dû le dire / étiqueter.
KitsuneYMG
12

Il s'agit de la proposition initiale d'ajout de ce mot clé. Comme l'a souligné dirkgently, il s'agit d'une fonctionnalité C99 ; cela n'a rien à voir avec C ++.

se détendre
la source
6
De nombreux compilateurs C ++ prennent en charge le __restrict__mot - clé qui est identique pour autant que je sache.
Robert S. Barnes
Cela a tout à voir avec C ++, car les programmes C ++ appellent les bibliothèques C et les bibliothèques C utilisent restrict. Le comportement du programme C ++ devient indéfini s'il enfreint les restrictions impliquées parrestrict .
Kaz le
@kaz Totalement faux. Cela n'a rien à voir avec C ++ car ce n'est pas un mot clé ou une fonctionnalité de C ++, et si vous utilisez des fichiers d'en-tête C en C ++, vous devez supprimer le restrictmot clé. Bien sûr, si vous passez des pointeurs aliasés à une fonction C qui les déclare restreints (ce que vous pouvez faire à partir de C ++ ou C), alors ils ne sont pas définis, mais c'est à vous.
Jim Balter
@JimBalter Je vois, donc ce que vous dites, c'est que les programmes C ++ appellent les bibliothèques C et les bibliothèques C les utilisent restrict. Le comportement du programme C ++ devient indéfini s'il enfreint les restrictions impliquées par restrict. Mais cela n'a en fait rien à voir avec C ++, car c'est "sur vous".
Kaz
4

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

Johan Boulé
la source
3
Je suppose que cela est soit géré en utilisant une extern Cdéclaration, soit en la supprimant silencieusement, comme c'est le cas avec le compilateur AIX C / C ++, qui gère à la place le __rerstrict__mot - clé. Ce mot-clé est également pris en charge sous gcc afin que le code compile le même sous g ++.
Robert S.Barnes
4

Il n'y a pas de tel mot-clé en C ++. La liste des mots-clés C ++ se trouve dans la section 2.11 / 1 de la norme du langage C ++. restrictest un mot clé de la version C99 du langage C et non du C ++.

Fourmi
la source
5
De nombreux compilateurs C ++ prennent en charge le __restrict__mot - clé qui est identique pour autant que je sache.
Robert S.Barnes
18
@Robert: Mais ce mot-clé n'existe pas en C ++ . Ce que font les compilateurs individuels, c'est leur propre entreprise, mais cela ne fait pas partie du langage C ++.
jalf
Des réponses comme celle-ci sont. C'est très utile. Cela pourrait être un commentaire à la question d'origine, mais si le véritable objectif de la question est suffisamment évident (le but du mot-clé restrict), il est préférable d'ajouter la vraie réponse après le petit bout de papier.
nteissler le