Question

Je parcourais une documentation et questions / réponses et 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 un autre endroit.

Quelqu'un peut-il offrir certains cas réalistes où sa valeur en utilisant ce fait?

Était-ce utile?

La solution

restrict dit que le pointeur est la seule chose qui accède à l'objet sous-jacent. Il élimine le risque d'aliasing pointeur, ce qui permet une meilleure optimisation par le compilateur.

Par exemple, supposons que j'ai une machine avec des instructions spécialisées qui peuvent se multiplier des vecteurs de nombres dans la mémoire, et je 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 se chevauchent src2, ce qui signifie qu'il doit faire une multiplication à la fois, du début à la fin. En ayant restrict, le compilateur est libre d'optimiser ce code à l'aide des instructions vectorielles.

Wikipedia a une entrée sur restrict, avec un autre exemple, .

Autres conseils

Le Wikipédia exemple est très éclairer.

Il montre clairement comment permet d'enregistrer une instruction de montage .

Sans restreindre:

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

Ensemble de Pseudo:

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 restrict:

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

Ensemble de Pseudo:

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-GCC vraiment le faire?

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, le appelant la convention est:

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

sortie GCC a été encore plus clair que l'article wiki: 4 instructions vs 3 instructions

.

Tableaux

Jusqu'à présent, nous avons des économies d'instruction unique, mais si le pointeur représentent des tableaux pour être mis en boucle sur un cas d'utilisation commune, puis un tas d'instructions pourraient être sauvées, comme mentionné par supercat .

Prenons 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 à:

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

qui est potentiellement beaucoup plus efficace car il peut être optimisé sur l'assemblage d'une mise en œuvre de libc décent (comme glibc): Est-il préférable d'utiliser std :: memcpy () ou std :: copy () en termes de performance?

Est-GCC vraiment le faire?

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 restrict:

    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 restreindre: aucun appel stdlib, à seulement 16 itération large boucle qui href="https://en.wikipedia.org/wiki/Loop_unrolling" le déroulement Je ne veux pas reproduire ici: -)

Je ne l'ai pas eu la patience de référence, mais je crois que la version restreindre sera plus rapide.

C99

Regardons la norme pour être complet.

restrict dit que deux pointeurs ne peuvent pas pointer vers chevauchement des zones de mémoire. L'utilisation la plus courante est pour les arguments de la fonction.

Ceci limite la façon dont la fonction peut être appelée, mais permet plus d'optimisations de compilation.

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

Le C99 N1256 projet 6.7. 3/7 "qualifications de type" dit:

  

L'utilisation prévue du qualificatif restreindre (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 prétraitements composant un programme conforme ne change pas de sens (par exemple, le comportement observable).

et 6.7.3.1 « Définition formelle de restreindre » donne les détails sordides.

règle stricte aliasing

Le mot-clé restrict affecte uniquement les pointeurs de types compatibles (par exemple deux de int*) parce que les règles strictes d 'alias dit que l'aliasing des types incompatibles est un comportement non défini par défaut, et ainsi compilateurs peut supposer qu'il ne se produit pas et d'optimiser l'écart.

Voir: Quelle est la règle stricte de aliasing

Voir aussi

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top