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?
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?
#warning
directive commune , ou les macros de signature de fonction ( __PRETTY_FUNCTION__
sur GCC, __FUNCSIG__
sur MSVC, etc.).
restrict
n'est pas considéré comme un mot clé C ++ (voir en.cppreference.com/w/cpp/keyword ), et en fait, la seule mention de restrict
dans 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:
restrict
doit ê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 restrict
en C, le restrict
mot - clé doit être supprimé de la signature de l'équivalent C ++.
Réponses:
Dans son article, Memory Optimization , Christer Ericson dit 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 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 __restrict
mot clé .
#ifndef __GNUC__
#define __restrict__ /* no-op */
ou similaire. Et définissez-le comme __restrict
si _MSC_VER
est défini.
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
restrict
dit 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 restrict
contrat, 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ètrersi
= deuxième paramètrerdx
= troisième paramètreLa 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 for
version fait:
p1 == {4, 4, 4, 9}
tandis que la memset
version 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 memset
appels 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 restrict
mot-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 this
les fonctions membres:
void T::fn () __restrict__
-fno-strict-aliasing
, alors restrict
ne 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")
restrict
signifie quelque chose en C ++. Si vous appelez une fonction de bibliothèque C avec des restrict
paramètres d'un programme C ++, vous devez obéir aux implications de cela. Fondamentalement, si restrict
est 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.
Rien. Il a été ajouté à la norme C99.
restrict
comme 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.
__restrict__
mot - clé qui est identique pour autant que je sache.
restrict
. Le comportement du programme C ++ devient indéfini s'il enfreint les restrictions impliquées parrestrict
.
restrict
mot 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.
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".
É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é .
extern C
dé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 ++.
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 ++. restrict
est un mot clé de la version C99 du langage C et non du C ++.
__restrict__
mot - clé qui est identique pour autant que je sache.
restrict
est 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.