Comment dire à GCC qu'un argument pointeur est toujours Double-word-aligned?
dans mon programme j'ai une fonction qui fait une addition vectorielle simple c[0:15] = a[0:15] + b[0:15]
. La fonction prototype est:
void vecadd(float * restrict a, float * restrict b, float * restrict c);
sur notre architecture embarquée 32 bits il y a une option de chargement / stockage de mots doubles, comme:
r16 = 0x4000 ;
strd r0,[r16] ; stores r0 in [0x4000] and r1 in [0x4004]
L'optimiseur GCC reconnaît la nature vectorielle de la boucle et génère deux branches du code - une pour le cas où les 3 tableaux sont alignés en deux mots (donc il utilise le double charger / stocker les instructions) et l'autre pour le cas où les tableaux sont alignés sur les mots (où il utilise l'option de chargement/stockage unique).
le problème est que le contrôle d'alignement d'adresse est coûteux par rapport à la partie d'ajout et je veux l'éliminer en suggérant le compilateur que a, b et c sont toujours 8-alignés. Est-il un modificateur à ajouter à la déclaration de pointeur de le dire au compilateur?
les tableaux qui sont utilisés pour appeler cette fonction avoir l'attribut aligned(8), mais il n'est pas reflété dans le code de fonction lui-même. est-il possible d'ajouter cet attribut pour les paramètres de la fonction?
6 réponses
en suivant un exemple de code que j'ai trouvé sur mon système, j'ai essayé la solution suivante, qui incorpore des idées à partir de quelques-unes des réponses données plus tôt: fondamentalement, créer une union d'un petit tableau de flotteurs avec un type 64 bits - dans ce cas un vecteur SIMD de flotteurs - et appeler la fonction avec un cast de l'opérande flotteurs:
typedef float f2 __attribute__((vector_size(8)));
typedef union { f2 v; float f[2]; } simdfu;
void vecadd(f2 * restrict a, f2 * restrict b, f2 * restrict c);
float a[16] __attribute__((aligned(8)));
float b[16] __attribute__((aligned(8)));
float c[16] __attribute__((aligned(8)));
int main()
{
vecadd((f2 *) a, (f2 *) b, (f2 *) c);
return 0;
}
maintenant le compilateur ne génère pas la branche alignée en 4.
cependant, le __builtin_assume_aligned()
serait la solution préférable, la prévention de la fonte et les effets secondaires possibles, si elle seulement fonctionné...
EDIT: j'ai remarqué que la fonction builtin est en fait buggée sur notre implémentation (I. e, non seulement il ne fonctionne pas, mais il provoque des erreurs de calcul plus tard dans le code.
si les attributs ne fonctionnent pas, ou ne sont pas une option ....
Je ne suis pas sûr, mais essayez ceci:
void vecadd (float * restrict a, float * restrict b, float * restrict c)
{
a = __builtin_assume_aligned (a, 8);
b = __builtin_assume_aligned (b, 8);
c = __builtin_assume_aligned (c, 8);
for ....
qui devrait dire à GCC que les pointeurs sont alignés. De là, si elle fait ce que vous voulez dépend si le compilateur peut utiliser cette information efficacement; il pourrait ne pas être assez intelligent: ces optimisations ne sont pas faciles.
une Autre option pourrait être d'envelopper le flotteur à l'intérieur d'une union qui doit être de 8 octets aligné:
typedef union {
float f;
long long dummy;
} aligned_float;
void vedadd (aligned_float * a, ......
je pense que cela devrait imposer l'alignement de 8 octets, mais encore une fois, je ne sais pas si le compilateur est assez intelligent pour l'utiliser.
Comment dire à GCC qu'un argument pointeur est toujours double-mot-aligné?
il semble que les nouvelles versions de GCC ont __builtin_assume_aligned
:
fonction intégrée:
void * __builtin_assume_aligned (const void *exp, size_t align, ...)
Cette fonction renvoie son premier argument, et permet au compilateur de supposer que le pointeur retourné est au moins aligner octets alignés. Cet élément peut avoir soit deux ou trois arguments, si il a trois, le troisième argument devrait avoir le type entier, et s'il est non nul moyens de désalignement de l'offset. Par exemple:
void *x = __builtin_assume_aligned (arg, 16);
signifie que le compilateur peut supposer que x, réglé sur arg, est aligné d'au moins 16 octets, tandis que:
void *x = __builtin_assume_aligned (arg, 32, 8);
signifie que le compilateur peut supposer pour x, défini à arg, que (char *) x - 8 est aligné sur 32 octets.
basé sur d'autres questions et réponses sur le débordement de la pile vers 2010, il semble que L'intégration n'était pas disponible dans GCC 3 et au début de GCC 4. Mais je ne sais pas où est le point de coupure.
les versions gcc ont été douteuse au sujet de align() sur des typographies et des tableaux simples. Typiquement pour faire ce que vous voulez, vous devez envelopper le flotteur dans une structure, et avoir le flotteur contenu a la restriction d'alignement.
avec la surcharge de l'opérateur, vous pouvez rendre cela presque indolore, mais il suppose que vous pouvez utiliser la syntaxe c++.
#include <stdio.h>
#include <string.h>
#define restrict __restrict__
typedef float oldfloat8 __attribute__ ((aligned(8)));
struct float8
{
float f __attribute__ ((aligned(8)));
float8 &operator=(float _f) { f = _f; return *this; }
float8 &operator=(double _f) { f = _f; return *this; }
float8 &operator=(int _f) { f = _f; return *this; }
operator float() { return f; }
};
int Myfunc(float8 * restrict a, float8 * restrict b, float8 * restrict c);
int MyFunc(float8 * restrict a, float8 * restrict b, float8 * restrict c)
{
return *c = *a* *b;
}
int main(int argc, char **argv)
{
float8 a, b, c;
float8 p[4];
printf("sizeof(oldfloat8) == %d\n", (int)sizeof(oldfloat8));
printf("sizeof(float8) == %d\n", (int)sizeof(float8));
printf("addr p[0] == %p\n", &p[0] );
printf("addr p[1] == %p\n", &p[1] );
a = 2.0;
b = 7.0;
MyFunc( &a, &b, &c );
return 0;
}
ne s'appliquent habituellement qu'aux alignements plus petits que le type de base d'un pointeur, et non plus grands.
je pense que le plus facile est de déclarer votre tableau entier avec une spécification d'alignement, quelque chose comme
typedef float myvector[16];
typedef myvector alignedVector __attribute__((aligned (8));
(La syntaxe pourrait ne pas être correcte, j'ai toujours des difficultés à savoir où mettre ces __attribute__
s)
et utilisez ce type dans votre code. Pour votre définition de fonction J'essaierais
void vecadd(alignedVector * restrict a, alignedVector * restrict b, alignedVector * restrict c);
cela vous donne une expression indirecte supplémentaire mais ce n'est que de la syntaxe. Quelque chose comme *a
est juste un noop et seulement réinterprète le pointeur comme un pointeur vers le premier élément.
Je ne l'ai jamais utilisé, mais il y a _ attribut _(aligné (8)))
si je lis la documentation correctement, alors elle est utilisée de cette façon:
void vecadd(float * restrict a __attribute__((aligned (8))),
float * restrict b __attribute__((aligned (8))),
float * restrict c __attribute__((aligned (8))));
voir http://ohse.de/uwe/articles/gcc-attributes.html#type-aligned