Que signifie le mot-clé restrict en C++?
j'étais toujours incertain, que signifie le mot-clé restrict en C++?
signifie que les deux ou pointeur de donnée à la fonction ne se chevauchent pas? Quoi d'autre?
6 réponses
dans son article, optimisation de la mémoire , Christer Ericson dit que bien que restrict
ne fasse pas encore partie de la norme C++, qu'il est supporté par de nombreux compilateurs et il recommande son utilisation quand disponible:
restreindre mot-clé
! Nouveau à la norme ANSI/ISO C 1999
! Pas encore en standard C++, mais pris en charge par de nombreux compilateurs C++
! Un conseil seulement, donc peut ne rien faire et encore conforme
restreindre l'qualifiés pointeur (ou de référence)...
! ...est fondamentalement un promesse au compilateur que pour la portée du pointeur, la cible du pointeur ne être accessible à travers ce pointeur (et les pointeurs copiés à partir d'elle).
dans les compilateurs C++ qui le supportent, il devrait probablement se comporter de la même façon que en C.
de Voir cette SI post pour plus de détails: Réaliste utilisation du C99 "limiter" mot?
prenez une demi-heure pour parcourir le journal D'Ericson, c'est intéressant et ça en vaut la peine.
Modifier
j'ai aussi trouvé que le compilateur AIX C/C++ d'IBM supporte le __restrict__
mot-clé .
g++ semble aussi supporter ceci comme 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 parcouru un article qui traite spécifiquement de l'utilisation de restrict dans les programmes C++:
Load-hit-magasins et de l' __restreindre mot-clé
aussi, Microsoft Visual C++ supporte aussi le __restrict
mot-clé .
comme d'autres ont dit, si signifie rien à partir de C++14 , alors considérons le __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 se chevauchant. L'usage le plus courant est pour les arguments de fonction.
cela restreint la façon dont la fonction peut être appelée, mais permet plus d'optimisations de compilations.
si l'appelant ne respecte pas le contrat restrict
, comportement non défini.
Le C99 N1256 projet 6.7.3/7 de Type "qualificatifs" dit:
l'utilisation prévue du qualificatif restrict (comme la register storage class) est de promouvoir l'optimisation, et de supprimer 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.-à-d., comportement observable).
et 6.7.3.1 "définition Formelle de restreindre" donne les détails sanglants.
une optimisation possible
le exemple Wikipédia est très illuminant.
il montre clairement comment comme il permet de sauver un ensemble instruction .
sans restriction:
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 restriction:
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
, ils 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
= second paramètre -
rdx
= troisième paramètre
la sortie GCC était encore plus claire que l'article wiki: 4 instructions vs 3 instructions.
matrices
jusqu'à présent nous avons des économies d'instruction simples, mais si pointer représentent des tableaux à boucler, un cas d'utilisation commune, alors un tas d'instructions pourrait être enregistré, 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;
}
}
à cause de restrict
, un compilateur intelligent (ou humain), pourrait optimiser cela à:
memset(p1, 4, size);
memset(p2, 9, size);
qui est potentiellement beaucoup plus efficace puisqu'il peut être optimisé pour l'assemblage sur une implémentation libc décente (comme glibc) est-il préférable d'utiliser std::memcpy() ou std::copy() en termes de performance? , éventuellement avec SIMD instructions .
sans, restreindre, cette optimisation ne pouvait pas être faite, par exemple considérer:
char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);
Puis 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 restriction:
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 "1519120920"x4,%esi 3ff: 48 89 d3 mov %rdx,%rbx 402: 48 83 ec 08 sub "1519120920"x8,%rsp 406: e8 00 00 00 00 callq 40b <fr+0x1b> 407: R_X86_64_PC32 memset-0x4 40b: 48 83 c4 08 add "1519120920"x8,%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 "1519120920"x9,%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
comme prévu. -
sans restriction: pas d'appels stdlib, juste une itération 16 large boucle déroulante que je n'ai pas l'intention de reproduire ici : -)
Je n'ai pas eu la patience de les comparer, mais je crois que la version de restriction sera plus rapide.
Strict règle d'identification
le mot-clé restrict
n'affecte que les pointeurs de types compatibles (par exemple deux int*
) parce que les règles d'aliasing strictes indiquent que l'aliasing de types incompatibles est un comportement non défini par défaut, et donc les compilateurs peuvent supposer qu'il ne se produit pas et optimiser loin.
Voir: qu'est-Ce que la stricte aliasing règle?
fonctionne pour les références?
selon les documents du CCG il fait: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html avec syntaxe:
int &__restrict__ rref
il existe même une version pour this
des fonctions des membres:
void T::fn () __restrict__
il n'y a pas de mot clé dans C++. La liste des mots-clés C++ se trouve dans la section 2.11/1 de la norme de langage C++. restrict
est un mot clé dans la version C99 du langage C et non dans C++.
puisque 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é, de sorte que nous n'avons pas à #définir le mot-clé à une macro vide pour supprimer le mot-clé.