Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

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
Taurre
On 4 mai, 13:56, Antoine Leca wrote:
Taurre écrivit :

>> En fait je le vois comme une partition de l'espace des objets en n+1
>> ensembles, où n est le nombre de pointeurs restreints. Dans la derni ère
>> partie, les objets peuvent être atteints par différents pointeurs
>> (alias), mais ne peuvent être atteints par l'un quelconque des n
>> pointeurs restreints ou une de leurs dérivations.

> C'est aussi l'image que j'ai en tête, mais le problème c'est que le s
> exemples donné par la Norme ne semble pas tout à fait la suivre. En
> effet, si je reprends mon exemple du début où seul le pointeur "v" est
> qualifié de restreint, dans ce cas on se retrouve avec deux
> partitions: une pour les objets accédés par "v" et une pour les aut res
> (si j'ai bien saisit?).

Oui.

> En suivant cette logique, on pourrait donc
> supposer que l'optimisation aura lieu,

Non. C'est tout l'onjet de mon premier message : le fait est que l'on
ait une opportunité d'optimisation ne signifie pas obligatoirement que
le compilateur va effectivement réaliser la dite optimisation.

> cependant elle ne se produira que si tous les pointeurs sont
> qualifiés de restreint...

Euh, là c'est ton analyse du comportement de GCC. GCC est un gros
programme, je n'ai pas les possibilités intellectuelles pour détermin er
son comportement (qui par ailleurs varie avec les versions).

> La même
> analyse peut être appliquée au prototype des fonctions standard:
> pourquoi qualifié les deux pointeurs utilisé par la fonction memcpy de
> restreint alors que logiquement un seul suffit?

Parce que l'implémentation des fonctions de la bibliothèque standard
peut utiliser à sa guise d'autres objets (qui seront donc dans la
dernière partie). Si un seul des deux pointeurs était restreint, l'au tre
serait dans la dernière partie aussi, et cela empêcherait des
opportunités d'optimisation ; la norme ne peut pas arbitrairement faire
ce genre de choses, surtout quand il est si simple de faire bien.

De plus, la norme est une sorte d'exemple à suivre dans la manière de
déclarer les interfaces, et là aussi il est plus logique et plus clai r
de déclarer tous les pointeurs comme restreints.

Par ailleurs, je soupçonne que le fait de déclarer des paramètres c omme
restreint doive permettre à des analyseurs de détecter des cas de
confusion de variables, ou au moins de soulever des questions sur des
constructions litigieuses, qui auraient avantage à être réécrites de
manière plus claire.

> Je trouve la définition de la Norme franchement floue,

Disons que le style de cette partie tranche assez nettement avec le
reste ; c'est dû en partie au fait que les rédacteurs ne sont pas les mêmes.





> c'est limite si elle ne se contredit pas dans son exemple numero deux
> (6.7.3.1 § 8 p 111) quand elle dit:

>> EXAMPLE 2 The function parameter declarations in the following example
> void f(int n, int * restrict p, int * restrict q)
<snip>
>> assert that, during each execution of the function, if an object is ac cessed through
>> one of the pointer parameters, then it is not also accessed through th e other.

> alors qu'elle dit plus haut (6.7.3 § 7 p 109):

>> An object that is accessed through a restrict-qualified pointer has a special association
>> with that pointer. This association, defined in 6.7.3.1 below, require s that all accesses to
>> that object use, directly or indirectly, the value of that particular pointer.

Désolé, je ne vois aucune contradiction.

Antoine



Merci beaucoup pour cette réponse qui m'a pas mal éclairer sur la
question :)

Non. C'est tout l'enjeu de mon premier message : le fait est que l'on
ait une opportunité d'optimisation ne signifie pas obligatoirement que
le compilateur va effectivement réaliser la dite optimisation.



Mmmh... D'accord, il me faut donc demander du côté de GCC pourquoi il
ne profite pas de cette opportunité.

Parce que l'implémentation des fonctions de la bibliothèque standard
peut utiliser à sa guise d'autres objets (qui seront donc dans la
dernière partie). Si un seul des deux pointeurs était restreint, l'au tre
serait dans la dernière partie aussi, et cela empêcherait des
opportunités d'optimisation ; la norme ne peut pas arbitrairement faire
ce genre de choses, surtout quand il est si simple de faire bien.

De plus, la norme est une sorte d'exemple à suivre dans la manière de
déclarer les interfaces, et là aussi il est plus logique et plus clai r
de déclarer tous les pointeurs comme restreints.

Par ailleurs, je soupçonne que le fait de déclarer des paramètres c omme
restreint doive permettre à des analyseurs de détecter des cas de
confusion de variables, ou au moins de soulever des questions sur des
constructions litigieuses, qui auraient avantage à être réécrites de
manière plus claire.



Il est vrai que le fait de ne déclarer qu'un seul pointeur dans les
prototypes aurait paru quelques peu incohérent.
Sinon, par curiosité, quelles opportunités d'optimisation pourraient-
être perdues? Auriez-vous un exemple?

Désolé, je ne vois aucune contradiction.



En fait, cela se rapportait à la question: "pourquoi les deux
pointeurs sont qualifiés de restreint"?
Avatar
Antoine Leca
Taurre écrivit :
On 4 mai, 13:56, Antoine Leca wrote:
Taurre écrivit :
La même
analyse peut être appliquée au prototype des fonctions standard:
pourquoi qualifié les deux pointeurs utilisé par la fonction memcpy de
restreint alors que logiquement un seul suffit?



Parce que l'implémentation des fonctions de la bibliothèque standard
peut utiliser à sa guise d'autres objets (qui seront donc dans la
dernière partie). Si un seul des deux pointeurs était restreint, l'autre
serait dans la dernière partie aussi, et cela empêcherait des
opportunités d'optimisation ; la norme ne peut pas arbitrairement faire
ce genre de choses, surtout quand il est si simple de faire bien.



Sinon, par curiosité, quelles opportunités d'optimisation pourraient-
être perdues? Auriez-vous un exemple?



Avec memcpy ? Mmmm... environnement multi-ALU, avec des coprocesseurs
(genre DMA ou blitter) capables de faire des copies de zones mémoires
indépendament ; si la source et la destination sont restreintes, le
compilateur peut supposer que le code environnant n'agira pas dessus, y
compris par effet de bord des autres coprocesseurs, et donc lancer la
copie sur le coprocesseur sans primitive de synchronisation. Sans la
condition de restriction, les primitives de synchronisation peuvent
devenir nécessaire, et on perd donc une occasion d'optimisation.

Et si tu trouves que c'est tiré par les cheveux, on peut être d'accord.
Mais je pense qu'en fixant quelques axiomes, on doit pouvoir arriver à
des conditions réalistes qui peuvent justifier la suppression d'une
optimisation du fait de la suppression d'un qualificatif restrict dans
une déclaration de la bibliothèque standard.


Antoine
Avatar
Taurre
On 5 mai, 09:40, Antoine Leca wrote:
Taurre écrivit :



> On 4 mai, 13:56, Antoine Leca wrote:
>> Taurre écrivit :
>>> La même
>>> analyse peut être appliquée au prototype des fonctions standard:
>>> pourquoi qualifié les deux pointeurs utilisé par la fonction memc py de
>>> restreint alors que logiquement un seul suffit?

>> Parce que l'implémentation des fonctions de la bibliothèque standa rd
>> peut utiliser à sa guise d'autres objets (qui seront donc dans la
>> dernière partie). Si un seul des deux pointeurs était restreint, l 'autre
>> serait dans la dernière partie aussi, et cela empêcherait des
>> opportunités d'optimisation ; la norme ne peut pas arbitrairement fa ire
>> ce genre de choses, surtout quand il est si simple de faire bien.

> Sinon, par curiosité, quelles opportunités d'optimisation pourraien t-
> être perdues? Auriez-vous un exemple?

Avec memcpy ? Mmmm... environnement multi-ALU, avec des coprocesseurs
(genre DMA ou blitter) capables de faire des copies de zones mémoires
indépendament ; si la source et la destination sont restreintes, le
compilateur peut supposer que le code environnant n'agira pas dessus, y
compris par effet de bord des autres coprocesseurs, et donc lancer la
copie sur le coprocesseur sans primitive de synchronisation. Sans la
condition de restriction, les primitives de synchronisation peuvent
devenir nécessaire, et on perd donc une occasion d'optimisation.

Et si tu trouves que c'est tiré par les cheveux, on peut être d'accor d.
Mais je pense qu'en fixant quelques axiomes, on doit pouvoir arriver à
des conditions réalistes qui peuvent justifier la suppression d'une
optimisation du fait de la suppression d'un qualificatif restrict dans
une déclaration de la bibliothèque standard.

Antoine



C'est vrai que l'exemple est peut-être un peut tiré par les cheveux,
mais il a le mérite d'être concret et réaliste.
En tous les cas, merci infiniment pour vos réponses, j'y vois à
présent beaucoup plus clair sur le qualificatif restrict :)
Avatar
Taurre
Je viens d'avoir ma réponse du côté de GCC: http://gcc.gnu.org/bugzil la/show_bug.cgi?idH885.
Alors, en fait il s'agit en partie d'un problème d'interprétation de
la Norme, mais surtout d'un problème de capacité d'analyse. En effet,
chose que j'ignorais, il est tout à fait possible d'effectuer des
assignations entre pointeurs restreints et non restreints (cf ce lien:
http://www.lysator.liu.se/c/restrict.html#assignments-to-unrestricted);
il devient dès lors difficile pour le compilateur de détecter les
pointeurs basés sur des pointeurs restreints, et par voie de
conséquence, d'effectuer une optimisation. Voilà pourquoi, si l'on
souhaite maximiser les possibilités d'optimisation, il est nécessaire
de qualifier tous les pointeurs de restreints (et pourquoi
l'optimisation n'a pas lieu dans mon premier exemple).
Avatar
Taurre
Petite correction dans le lien sur les assignations entre pointeurs
restreints et non restreints: http://www.lysator.liu.se/c/restrict.html#assignments-to-unrestricted
Avatar
Taurre
Je me rends compte que les compilateurs doivent également jongler avec
le fait qu'il est tout à fait possible d'accéder à l'objet d'un
pointeur restreint via un autre pointeur, si cet accès ne vise pas à
modifier l'objet pointé (6.7.3.1 § 4 p 110):

During each execution of B, let L be any lvalue that has &L based on P. I f L is used to
access the value of the object X that it designates, AND X IS ALSO MODIFI ED (by any means),
then the following requirements apply: [...]



Cela explique pourquoi un tel exemple ne peut pas être optimisé:

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


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


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

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

puisque l'objet accédé par "v" peut être modifié via "a" ou "b".
Avatar
espie
Lors des messages initiaux de cette discussion, il m'etait vaguement revenu
en memoire que, effectivement, restrict ne sert a rien.

Plus precisement, c'est le thread de discussion sur gcc auquel tu fais
reference, qui expose que, dans enormement de cas pratiques, il est
virtuellement impossible d'utiliser restrict pour faire de l'optimisation,
parce qu'il faut en gros analyser tout le code, et que faire la propagation
d'alias correctement n'est pas simple (sans compter la semantique des
affectations de restrict a !restrict qui n'est pas totalement intuitive).

Si je me souviens bien, la conclusion de la discussion, c'etait que restrict
etait une fausse bonne idee, et que dans l'enorme majorite des cas, le code
produit par le compilo en ignorant restrict etait totalement equivalent
au code produit par le compilo en utilisant les maigres informations
donnees par restrict.
Avatar
Marc
Taurre wrote:

Cela explique pourquoi un tel exemple ne peut pas être optimisé:



Tu devrais installer soit le compilateur d'Intel soit celui d'Oracle.
Les 2 parviennent à optimiser dès que soit a soit c est restrict (b en
revanche ne suffit pas).
Avatar
Taurre
On 5 mai, 23:14, Marc wrote:
Taurre  wrote:
> Cela explique pourquoi un tel exemple ne peut pas être optimisé:

Tu devrais installer soit le compilateur d'Intel soit celui d'Oracle.
Les 2 parviennent à optimiser dès que soit a soit c est restrict (b e n
revanche ne suffit pas).



Intéressant, il va falloir que je test ces compilateurs. Par contre,
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?

Marc Espie écrivit:

Lors des messages initiaux de cette discussion, il m'etait vaguement reven u
en memoire que, effectivement, restrict ne sert a rien.



Je n'irais peut-être pas jusqu'à dire qu'il ne sert à rien, mais c'es t
vrai que mis à part quelques cas spécifiques son utilisation ne semble
pas apporter grand chose... J'étais tombé sur cet article, assez
intéressant traitant du sujet:
http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restr ict-keyword.html
qui, dans ses sources, renseignait ce pdf: http://www.cs.pitt.edu/~mock/pap ers/clei2004.pdf.
Ce dernier présente une analyse de performance par rapport à
l'utilisation de pointeurs restreints, les résultats sont assez
décevants (en moyen un gain de 1% et, dans le meilleur des cas, un
gain de 8%).
Avatar
Marc
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.
1 2 3 4