OVH Cloud OVH Cloud

Question sur les pointeurs restreints

32 réponses
Avatar
Taurre
Bonjour =E0 tous,

Je me suis r=E9cemment pench=E9 sur la notion de pointeurs restreints
introduite par la Norme C99 et il y a plusieurs questions auxquelles
je n'ai trouv=E9 aucune r=E9ponse:

- la premi=E8re porte sur ce code:

#include <stdlib.h>


void
test (int *a, int *b, int * restrict v)
{
*a =3D *v;
*b =3D *v;
}


int
main (void)
{
int a;
int b;
int v =3D 10;

test (&a, &b, &v);
return EXIT_SUCCESS;
}

si j'ai bien saisit la notion de pointeur restreint cela signifie,
dans le cas de la fonction "test", que l'objet sur lequel pointe la
variable "v" ne peut-=EAtre acc=E9der que via cette derni=E8re. Donc, en
toute logique, on peut supposer que l'objet point=E9 par la variable "v"
ne sera pas modifi=E9 lors de l'ex=E9cution de cette fonction. Or, si je
compile ce code, j'obtiens ces lignes en assembleur pour la fonction
"test":

movl (%rdx), %eax
movl %eax, (%rdi)
movl (%rdx), %eax
movl %eax, (%rsi)

la valeur de l'objet point=E9 par la variable "v" est charg=E9 deux fois
dans le registre eax alors qu'elle ne devrait l'=EAtre qu'une seule
fois... l'optimisation n'aura lieu que si un ou les deux autres
pointeurs sont =E9galement d=E9clar=E9 avec le qualificatif "restrict".
Quelqu'un pourrait-il m'expliquer pourquoi?

- la deuxi=E8me porte sur le m=EAme code, mais avec des variables de type
char. Dans cette hypoth=E8se, m=EAme si les trois pointeurs sont d=E9clar=
=E9s
avec le qualificatif "restrict", aucune optimisation n'est effectu=E9e
et j'obtiens donc:

movzbl (%rdx), %eax
movb %al, (%rdi)
movzbl (%rdx), %eax
movb %al, (%rsi)

Je pr=E9cise que je compile avec GCC et l'option d'optimisation -O2
Merci d'avance pour vos r=E9ponses.

10 réponses

1 2 3 4
Avatar
Antoine Leca
Marc Espie écrivit :
Si je me souviens bien, la conclusion de la discussion, c'etait que restrict
etait une fausse bonne idee,



Tu veux faire référence à <msgid:, 2e partie ?
http://groups.google.com/groups? si votre
fournisseur NNTP est un peu court en mémoire de masse ;-)


Antoine
Avatar
espie
In article <iq0abm$96q$,
Marc wrote:
Taurre wrote:

n'est ce pas un peu excessif d'optimiser lorsque "a" ou "b" (si son
affectation est la première) sont des pointeurs restreints? La Norme
permet d'accéder à un objet pointé par un pointeur restreint si
l'accès via un autre pointeur ne vise pas à le modifier, cela me
semble un peu risqué non?



Tout ce dont tu as besoin, c'est de savoir que a et v sont
indépendants. Pour gcc, ça veut dire les mettre tous les 2 restreints.
Pour Intel/Oracle, ça veut dire en mettre au moins un des 2 restreint.



Justement, je crois qu'il y avait des soucis d'interpretation de la norme,
et que dans ce cas precis, ce que compilent Intel/Oracle, on avait conclu
que ce n'etait pas du C standard...
Avatar
Marc
Marc Espie wrote:

Justement, je crois qu'il y avait des soucis d'interpretation de la norme,
et que dans ce cas precis, ce que compilent Intel/Oracle, on avait conclu
que ce n'etait pas du C standard...



Ce qui est clair, c'est qu'il y aurait besoin d'une clarification dans le standard...
Avatar
Pierre Habouzit
On 2011-04-23, Taurre wrote:
Bonjour à tous,

Je me suis récemment penché sur la notion de pointeurs restreints
introduite par la Norme C99 et il y a plusieurs questions auxquelles
je n'ai trouvé aucune réponse:

- la première porte sur ce code:

#include <stdlib.h>


void
test (int *a, int *b, int * restrict v)
{
*a = *v;
*b = *v;
}


[...]

la valeur de l'objet pointé par la variable "v" est chargé deux fois
dans le registre eax alors qu'elle ne devrait l'être qu'une seule
fois... l'optimisation n'aura lieu que si un ou les deux autres
pointeurs sont également déclaré avec le qualificatif "restrict".
Quelqu'un pourrait-il m'expliquer pourquoi?



parce qu'il est interdit de copier les valeurs de deux pointeurs
restrict entre eux (6.3.7.1) sans respecter pas mal de conditions, mais
il est totalement légal de faire:

void test(int *a, int *b, int * restrict v)
{
*a = *v;
*b = *v;
}

void test2(int *a, int * restrict v)
{
test(a, v, v);
}

Le standard demande que tous les accès aux "objets" pointés par v soient
fait par des expressions dérivées de la valeur du pointeur 'v' (§6.3.7
alinéa 7). Or ce que j'écris dans test2 est un cas où 'b' est dérivé de
'v', et donc a tout a fait le droit d'aliaser v.

L'appel précédent est interdit si le prototype de 'test' est:

void test(int *a, int * restrict b, int * restrict v)

car la règle assez convolue de '6.3.7.1 alinéa 4' interdit de lier 'b' à
'v' dans l'appel à test.

Donc gcc ne *PEUT PAS* faire l'optimisation car elle n'est pas légale si
il ne peut pas être sur que 'b' n'est pas dérivé de 'v'.

L'argument est bien sur symétrique pour 'a'.

--
·O· Pierre Habouzit
··O
OOO http://www.madism.org
Avatar
Taurre
On 26 mai, 01:25, Pierre Habouzit wrote:
On 2011-04-23, Taurre wrote:



> Bonjour à tous,

> Je me suis récemment penché sur la notion de pointeurs restreints
> introduite par la Norme C99 et il y a plusieurs questions auxquelles
> je n'ai trouvé aucune réponse:

> - la première porte sur ce code:

> #include <stdlib.h>

> void
> test (int *a, int *b, int * restrict v)
> {
>         *a = *v;
>         *b = *v;
> }
[...]

> la valeur de l'objet pointé par la variable "v" est chargé deux foi s
> dans le registre eax alors qu'elle ne devrait l'être qu'une seule
> fois... l'optimisation n'aura lieu que si un ou les deux autres
> pointeurs sont également déclaré avec le qualificatif "restrict".
> Quelqu'un pourrait-il m'expliquer pourquoi?

parce qu'il est interdit de copier les valeurs de deux pointeurs
restrict entre eux (6.3.7.1) sans respecter pas mal de conditions, mais
il est totalement légal de faire:

    void test(int *a, int *b, int * restrict v)
    {
        *a = *v;
        *b = *v;
    }

    void test2(int *a, int * restrict v)
    {
        test(a, v, v);
    }

Le standard demande que tous les accès aux "objets" pointés par v soi ent
fait par des expressions dérivées de la valeur du pointeur 'v' (§6. 3.7
alinéa 7). Or ce que j'écris dans test2 est un cas où 'b' est dér ivé de
'v', et donc a tout a fait le droit d'aliaser v.

L'appel précédent est interdit si le prototype de 'test' est:

    void test(int *a, int * restrict b, int * restrict v)

car la règle assez convolue de '6.3.7.1 alinéa 4' interdit de lier 'b ' à
'v' dans l'appel à test.

Donc gcc ne *PEUT PAS* faire l'optimisation car elle n'est pas légale s i
il ne peut pas être sur que 'b' n'est pas dérivé de 'v'.

L'argument est bien sur symétrique pour 'a'.

--
·O·  Pierre Habouzit
··O                                                
OOO                                                http://www.madism.org



Mmmh... Je ne suis pas certains que cet exemple respecte la Norme.

Le standard demande que tous les accès aux "objets" pointés par v soi ent
fait par des expressions dérivées de la valeur du pointeur 'v' (§6. 3.7
alinéa 7). Or ce que j'écris dans test2 est un cas où 'b' est dér ivé de
'v', et donc a tout a fait le droit d'aliaser v.



Le point 6.7.3.1 § 4, indique bien "During each execution of B", "B"
étant le bloc contenant la déclaration du pointeur restreint.
Le pointeur 'v' étant un paramètre de la fonction test, le bloc en
question est donc celui de cette fonction. Or, dans ce bloc, la valeur
de 'b' n'est pas une expression dérivée de 'v'. Dès lors, ce pointeur
ne peut normalement pas être utilisé pour modifiée l'objet pointé p ar
'v'.

En tous las cas c'est comme cela que je comprend la Norme.
Avatar
Taurre
Avec ce post je crois que je viens de saisir la difficulté d'optimiser
un code comportant des pointeurs restreints et le choix des
développeurs de GCC.
En effet, si je reprends mon code du début:


#include <stdio.h>
#include <stdlib.h>


void
test (int *a, int *b, int * restrict v)
{
*a = *v;
*b = *v;
}

int
main (void)
{
int a;
int b;
int v = 10;

test (&a, &b, &v);
return EXIT_SUCCESS;
}

Si GCC profitait de toutes les opportunités données par le concept de
pointeur restreint, ce dernier optimiserai ce code. Cependant, pour
pouvoir le faire, cela impliquerai qu'il doive se souvenir de tous les
pointeurs basés sur un pointeur restreint. En effet, dans les mêmes
conditions, ce code devrait être optimisé:

#include <stdio.h>
#include <stdlib.h>



void
test (int *a, int *b, int * restrict v)
{
*a = *v;
*b = *v;
}

int
main (void)
{
int a;
int b;
int v = 10;

int * restrict pa = &a;
int * restrict pb = &b;

test (pa, pb, &v);
return EXIT_SUCCESS;
}

Or, avec un tel exemple, je comprend vite pourquoi les développeurs de
GCC ont décidé de n'optimisé que les accès via des pointeurs qualif iés
de restreints. Il devient vite impossible de se souvenir au travers
des appels quels pointeurs sont basés sur des pointeurs restreints ou
non.
Avatar
Pierre Habouzit
On 2011-06-05, Taurre wrote:
On 26 mai, 01:25, Pierre Habouzit wrote:
On 2011-04-23, Taurre wrote:



> Bonjour à tous,

> Je me suis récemment penché sur la notion de pointeurs restreints
> introduite par la Norme C99 et il y a plusieurs questions auxquelles
> je n'ai trouvé aucune réponse:

> - la première porte sur ce code:

> #include <stdlib.h>

> void
> test (int *a, int *b, int * restrict v)
> {
>         *a = *v;
>         *b = *v;
> }
[...]

> la valeur de l'objet pointé par la variable "v" est chargé deux fois
> dans le registre eax alors qu'elle ne devrait l'être qu'une seule
> fois... l'optimisation n'aura lieu que si un ou les deux autres
> pointeurs sont également déclaré avec le qualificatif "restrict".
> Quelqu'un pourrait-il m'expliquer pourquoi?

parce qu'il est interdit de copier les valeurs de deux pointeurs
restrict entre eux (6.3.7.1) sans respecter pas mal de conditions, mais
il est totalement légal de faire:

    void test(int *a, int *b, int * restrict v)
    {
        *a = *v;
        *b = *v;
    }

    void test2(int *a, int * restrict v)
    {
        test(a, v, v);
    }

Le standard demande que tous les accès aux "objets" pointés par v soient
fait par des expressions dérivées de la valeur du pointeur 'v' (§6.3.7
alinéa 7). Or ce que j'écris dans test2 est un cas où 'b' est dérivé de
'v', et donc a tout a fait le droit d'aliaser v.

L'appel précédent est interdit si le prototype de 'test' est:

    void test(int *a, int * restrict b, int * restrict v)

car la règle assez convolue de '6.3.7.1 alinéa 4' interdit de lier 'b' à
'v' dans l'appel à test.

Donc gcc ne *PEUT PAS* faire l'optimisation car elle n'est pas légale si
il ne peut pas être sur que 'b' n'est pas dérivé de 'v'.

L'argument est bien sur symétrique pour 'a'.

--
·O·  Pierre Habouzit
··O                                                
OOO                                                http://www.madism.org



Mmmh... Je ne suis pas certains que cet exemple respecte la Norme.

Le standard demande que tous les accès aux "objets" pointés par v soient
fait par des expressions dérivées de la valeur du pointeur 'v' (§6.3.7
alinéa 7). Or ce que j'écris dans test2 est un cas où 'b' est dérivé de
'v', et donc a tout a fait le droit d'aliaser v.



Le point 6.7.3.1 § 4, indique bien "During each execution of B", "B"
étant le bloc contenant la déclaration du pointeur restreint.



Oui mais il n'y a pas de bloc ici vu que c'est le paramètre d'une
fonction. Enfin plus précisément les expressions viennent d'ailleurs et
le compilateur ne peut a priori pas savoir.

Dans la pratique GCC part du principe que les pointeurs qualifiés de
restrict ne s'aliasent pas mais qu'ils peuvent aliaser des pointeurs non
restrict lorsqu'il n'est pas capable de savoir si ils sont dérivés d'un
pointeur restrict.

Par contre au contraire de ce que tu dis dans ton autre post, dans le
scope d'un bloc GCC est capable de tracker assez finement les restricts
comme j'ai pu le constater sur différents autres types de code. Mais à
travers des prototypes de fonctions c'est différent.
--
·O· Pierre Habouzit
··O
OOO http://www.madism.org
Avatar
Taurre
On 7 juin, 19:39, Pierre Habouzit wrote:
On 2011-06-05, Taurre wrote:



> On 26 mai, 01:25, Pierre Habouzit wrote:
>> On 2011-04-23, Taurre wrote:

>> > Bonjour à tous,

>> > Je me suis récemment penché sur la notion de pointeurs restreint s
>> > introduite par la Norme C99 et il y a plusieurs questions auxquelles
>> > je n'ai trouvé aucune réponse:

>> > - la première porte sur ce code:

>> > #include <stdlib.h>

>> > void
>> > test (int *a, int *b, int * restrict v)
>> > {
>> >         *a = *v;
>> >         *b = *v;
>> > }
>> [...]

>> > la valeur de l'objet pointé par la variable "v" est chargé deux fois
>> > dans le registre eax alors qu'elle ne devrait l'être qu'une seule
>> > fois... l'optimisation n'aura lieu que si un ou les deux autres
>> > pointeurs sont également déclaré avec le qualificatif "restric t".
>> > Quelqu'un pourrait-il m'expliquer pourquoi?

>> parce qu'il est interdit de copier les valeurs de deux pointeurs
>> restrict entre eux (6.3.7.1) sans respecter pas mal de conditions, mai s
>> il est totalement légal de faire:

>>     void test(int *a, int *b, int * restrict v)
>>     {
>>         *a = *v;
>>         *b = *v;
>>     }

>>     void test2(int *a, int * restrict v)
>>     {
>>         test(a, v, v);
>>     }

>> Le standard demande que tous les accès aux "objets" pointés par v soient
>> fait par des expressions dérivées de la valeur du pointeur 'v' ( §6.3.7
>> alinéa 7). Or ce que j'écris dans test2 est un cas où 'b' est d érivé de
>> 'v', et donc a tout a fait le droit d'aliaser v.

>> L'appel précédent est interdit si le prototype de 'test' est:

>>     void test(int *a, int * restrict b, int * restrict v)

>> car la règle assez convolue de '6.3.7.1 alinéa 4' interdit de lier 'b' à
>> 'v' dans l'appel à test.

>> Donc gcc ne *PEUT PAS* faire l'optimisation car elle n'est pas légal e si
>> il ne peut pas être sur que 'b' n'est pas dérivé de 'v'.

>> L'argument est bien sur symétrique pour 'a'.

>> --
>> ·O·  Pierre Habouzit
>> ··O                                                
>> OOO                                                http://www.madism.org

> Mmmh... Je ne suis pas certains que cet exemple respecte la Norme.

>> Le standard demande que tous les accès aux "objets" pointés par v soient
>> fait par des expressions dérivées de la valeur du pointeur 'v' ( §6.3.7
>> alinéa 7). Or ce que j'écris dans test2 est un cas où 'b' est d érivé de
>> 'v', et donc a tout a fait le droit d'aliaser v.

> Le point 6.7.3.1 § 4, indique bien "During each execution of B", "B"
> étant le bloc contenant la déclaration du pointeur restreint.

Oui mais il n'y a pas de bloc ici vu que c'est le paramètre d'une
fonction. Enfin plus précisément les expressions viennent d'ailleurs et
le compilateur ne peut a priori pas savoir.

Dans la pratique GCC part du principe que les pointeurs qualifiés de
restrict ne s'aliasent pas mais qu'ils peuvent aliaser des pointeurs non
restrict lorsqu'il n'est pas capable de savoir si ils sont dérivés d' un
pointeur restrict.

Par contre au contraire de ce que tu dis dans ton autre post, dans le
scope d'un bloc GCC est capable de tracker assez finement les restricts
comme j'ai pu le constater sur différents autres types de code. Mais à
travers des prototypes de fonctions c'est différent.
--
·O·  Pierre Habouzit
··O                                                
OOO                                                http://www.madism.org



J'ai en effet parler un peu vite pour ce qui est de la détection des
pointeurs basés sur les pointeurs restreints. Cela ne pose visiblement
problème que lors des appels de fonctions.

Par contre, je ne suis pas d'accord sur ce point:

Oui mais il n'y a pas de bloc ici vu que c'est le paramètre d'une
fonction. Enfin plus précisément les expressions viennent d'ailleurs et
le compilateur ne peut a priori pas savoir.

Dans la pratique GCC part du principe que les pointeurs qualifiés de
restrict ne s'aliasent pas mais qu'ils peuvent aliaser des pointeurs non
restrict lorsqu'il n'est pas capable de savoir si ils sont dérivés d' un
pointeur restrict.



Le standard précise bien que le bloc concerné est celui de la fonction
s'il s'agit d'un paramètre de fonction (6.7.3.1 §2 p 110):

If D appears in the list of parameter declarations of a function definiti on, let B
denote the associated block.



On peut donc présenter l'exemple avec les fonctions test et test2 sous
la forme de blocs et de sous-blocs:

int a;
int v = 10;

{
/* test2 */
int *a = &a;
int * restrict v = &v;

{
/* test */
int *a = a;
int *b = v;
int * restrict v = v;
}
}

Avec cet exemple on voit bien que 'b' n'est pas basé sur le pointeur
'v' du troisième bloc puisque ce dernier n'est pas encore défini.
Cependant, il alias le pointeur 'v' défini après puisqu'ils pointent
sur le même objet. On ne peut donc pas utiliser le pointeur 'b' pour
modifier cet objet sans craindre un comportement indéterminé.

Je suis d'accord sur le fait que GCC n'effectue pas d'optimisation
parce qu'il est difficile de détecter les pointeurs basés sur des
pointeurs restreints lors de passages en arguments. Par contre, pour
moi, l'exemple donné viole le standard.
Avatar
Taurre
Ok, donc je me suis complètement planté dans mon exemple, puisqu'un
identificateur est déclaré avant son initialisation, ce qui fait que
pratiquement toutes les variables s'assignent à elles-mêmes sans être
initialisées... Désolé pour cette erreur >_<
Avatar
Taurre
Après plusieurs relectures je pense avoir enfin compris le problème.
Si j'ai bien saisit, étant donné que les paramètres des fonctions se
voient attribués les valeurs des expressions passées en arguments lors
de l'appel, il se pose la question de savoir si, dans un tel cas, un
pointeur qui se voit assigner la même adresse qu'un pointeur restreint
peut être défini comme basé sur ce pointeur restreint. Les
développeurs de GCC ont décidé que oui et que, par conséquent, ces
deux pointeurs s'alias. Il s'ensuit une absence d'optimisation pour
mon premier code (étant donné qu'il y a un doute).

@Pierre Habouzit: merci infiniment pour cette explication :)
1 2 3 4