Le but de restrict n'est pas du tout d'empêcher le développeur ou le compilateur de faire des bêtises.
Le but est de préciser au compilo qu'il n'y a pas d'overlap.
Cette information permet au compilo de générer un code plus rapide dans certains cas.
Et le compilo ne peut généralement pas deviner cette information que l'on utilise -O3 ou pas. Pour le déterminer, il faut qu'il vérifie dans le source que lors des appels (Ou de l'appel, si la fonction est inliné) qu'il ne peut pas y avoir d'overlap car les paramètres sont "différents". Jolie casse tête. Et dans certains cas, c'est même tout à fait impossible car il ne peut pas connaître tous les appels d'avance (Cas d'une fonction exportée par une librairie).
Considérons une fonction plus proche de ce qui peut exister, c'est à dire une fonction updatePoints qui prend en paramètre :
1/ Un tableau de points(Coordonnées x et y).
2/ Le nombre d'élément du tableau.
3/ Un pointeur vers un point delta.
Le but de la fonction est d'ajouter à tous les points du tableau le delta contenu dans le paramètre trois.
1 2 3 4
| pour i de 0 à n - 1
points[i].x += delta.x;
points[i].y += delta.y;
fait; |
Ca revient à peu près à une bête translation d'un nuage de points dans un espace 2D. D'un point de vue fonctionnel, il n'y aura a jamais d'overlap entre "points" et "delta". Le programmeur le sait. Le compilo ne le sait pas forcément si le programmeur ne lui indique pas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <stdio.h>
#define POINT_COUNT 4
typedef struct
{
int x;
int y;
}
MY_POINT;
void updatePoints1(MY_POINT *lpPoints, int nPointCount, MY_POINT *lpDelta)
{
int i;
for (i = 0; i < nPointCount; i++)
{
lpPoints[i].x += lpDelta->x;
lpPoints[i].y += lpDelta->y;
}
}
void updatePoints2(MY_POINT *restrict lpPoints, int nPointCount, MY_POINT *restrict lpDelta)
{
int i;
for (i = 0; i < nPointCount; i++)
{
lpPoints[i].x += lpDelta->x;
lpPoints[i].y += lpDelta->y;
}
}
int main()
{
int i;
MY_POINT lpPoints[4];
MY_POINT delta;
for (i = 0; i < POINT_COUNT; i++)
{
lpPoints[i].x = i;
lpPoints[i].y = 100 + i;
}
delta.x = 1;
delta.y = 1;
updatePoints1(lpPoints, POINT_COUNT, &delta);
updatePoints2(lpPoints, POINT_COUNT, &delta);
for (i = 0; i < POINT_COUNT; i++)
{
printf("%d %d\n", lpPoints[i].x, lpPoints[i].y);
}
return 0;
} |
La différence entre updatePoints1 et updatePoints2 est simplement l'ajout du mot clé restrict. La différence se fait dans le code généré. En effet, comme restrict indique que delta n'est pas modifié quand lpPoints est modifié, la valeur de delta n'a pas à être lue à chaque tour de boucle.
Code sans restrict :
1 2 3 4 5 6 7 8
| loop:
mov ecx,[esi] # ecx = delta.x
mov eax,[esi+0x4] # eax = delta.y
add [ebx+edx*8],ecx # points[i].x += ecx
add [ebx+edx*8+0x4],eax # points[i].y += eax
inc edx # i++
cmp edx,0x4 # si i < 4 alors ...
jl loop # ... saute à loop |
Code avec restrict :
1 2 3 4 5 6 7 8
| mov ecx,[esi]
mov eax,[esi+0x4]
loop:
add [ebx+edx*8],ecx
add [ebx+edx*8+0x4],eax
inc edx
cmp edx,0x4
jl loop |
Le corps de la boucle (Ce qui est exécuté 1000 fois s'il y a 1000 éléments) a 2 instructions de moins. D'autre part, toujours dans le cas de restrict, on peut inverser la boucle et ainsi remplacer inc/cmp/jl par dec/jnz et gagner une autre instruction. On ne peut pas faire cette inversion sans restrict car si delta peut être un élément de points, le résultat peut dépendre du sens de parcourt.
Bref, restrict, c'est une information qui en théorie peut bien aider le compilo à générer du code plus rapide. Il semble donc intéressant de l'avoir dans la norme.
[ ************** edit ************** ]
A noter que dans les faits, avec les flags de compilation :
gcc -O3 test.c -o test.exe -std=c99 -fstrict-aliasing
gcc 3.4.5 (Version ancienne... Il semble qu'il y ait eu des amélioration de restrict en
4.5) ne réalise pas l'optimisation permise par restrict ci-dessus.
Par contre, on peut forcer gcc 3.4.5 à réaliser l'optimisation en faisant une copie du contenu de lpDelta dans une variable locale. Le code C paraît alors moins optimisé, mais le code machine généré est comme celui ci-dessus "avec restrict".
1 2 3 4 5 6 7 8 9 10 11 12 13
| void updatePoints1(MY_POINT *lpPoints, int nPointCount, MY_POINT *lpDelta)
{
int i;
MY_POINT delta;
delta = *lpDelta;
for (i = 0; i < nPointCount; i++)
{
lpPoints[i].x += delta.x;
lpPoints[i].y += delta.y;
}
} |
[ ************** edit 2 ************** ]
gcc 4.6.1 réalise correctement l'optimisation avec le mot clé restrict.
(Et il fait aussi l'optimisation en cas de copie locale de delta comme ci-dessus)
5 |
0 |