Utilisation réaliste du mot-clé C99 'restrict'?
Je parcourais de la documentation et des questions / réponses et je l'ai vu mentionné. J'ai lu une brève description, indiquant que ce serait essentiellement une promesse du programmeur que le pointeur ne sera pas utilisé pour pointer ailleurs.
Quelqu'un peut-il offrir des cas réalistes où il vaut la peine d'utiliser cela?
2 réponses
restrict
dit que le pointeur est la seule chose qui accède à l'objet sous-jacent. Il élimine le potentiel d'aliasing de pointeur, permettant une meilleure optimisation par le compilateur.
Par exemple, supposons que j'ai une machine avec des instructions spécialisées qui peuvent multiplier les vecteurs de nombres en mémoire, et j'ai le code suivant:
void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
for(int i = 0; i < n; i++)
{
dest[i] = src1[i]*src2[i];
}
}
Le compilateur doit gérer correctement si dest
, src1
, et src2
se chevauchent, ce qui signifie qu'il doit faire une multiplication à la fois, dès le début jusqu'à la fin. En ayant restrict
, le compilateur est libre d'optimiser ce code en utilisant les instructions vectorielles.
, Wikipédia a une entrée sur restrict
, avec un autre exemple, ici.
Le Wikipédia exemple est très éclairante.
, Il montre clairement comment , il permet d'enregistrer l'une des instructions de montage.
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 a may be equal to x.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b
, Avec de restreindre l':
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
load R2 ← *b
add R2 += R1
set R2 → *b
Est-ce que GCC le fait vraiment?
GCC 4.8 Linux x86-64:
gcc -g -std=c99 -O0 -c main.c
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
= deuxième paramètre -
rdx
= troisième paramètre
La sortie GCC était encore plus claire que l'article wiki: 4 instructions vs 3 instructions.
Les Matrices
Jusqu'à présent, nous avons des économies d'instructions simples, mais si le pointeur représente des tableaux à boucler, un cas d'utilisation commun, alors un tas d'instructions pourraient être sauvegardées, comme mentionné par supercat.
Considérons par exemple:
void f(char *restrict p1, char *restrict p2) {
for (int i = 0; i < 50; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
En raison de restrict
, un compilateur intelligent (ou humain), pourrait optimiser cela pour:
memset(p1, 4, 50);
memset(p2, 9, 50);
Qui est potentiellement beaucoup plus efficace car il peut être optimisé pour un 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?
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 les mêmes.
Avec -O3
:
-
, Avec de restreindre l':
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 restreindre: pas d'appels stdlib, juste une boucle de 16 itérations déroulement 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 restrict sera plus rapide.
C99
Regardons la norme pour l'exhaustivité.
restrict
indique que deux pointeurs ne peuvent pas pointer vers des régions de mémoire qui se chevauchent. L'utilisation la plus courante est pour les arguments de fonction.
Cela limite la façon dont la fonction peut être appelée, mais permet plus d'optimisations à la compilation.
Si l'appelant ne suit pas le contrat restrict
, comportement indéfini.
LeC99 n1256 projet 6.7.3/7 "Type les qualificatifs" dit:
L'utilisation prévue du qualificatif restrict (comme la classe de stockage register) 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 (comportement observable).
Et 6.7.3.1 "définition Formelle de restreindre" donne les détails sanglants.
Règle d'aliasing stricte
Le mot clé restrict
affecte uniquement pointeurs de types compatibles (par exemple deux int*
) parce que les règles d'aliasing strictes indiquent que l'aliasing des types incompatibles est un comportement indéfini par défaut, et donc les compilateurs peuvent supposer que cela ne se produit pas et optimiser loin.
Voir: Quelle est la règle d'aliasing stricte?
Voir aussi
- C++14 n'a pas encore d'analogue pour
restrict
, mais GCC a__restrict__
comme extension: Que signifie le mot-clé restrict en C++? - beaucoup questions qui demandent: selon les détails sanglants, ce code UB ou non?
- une question "quand utiliser": quand utiliser restrict et quand ne pas
- le GCC connexe
__attribute__((malloc))
, qui dit que le la valeur de retour d'une fonction n'est pas aliasée à quoi que ce soit: GCC: _ _ attribut__((malloc))