
ysap
En mi programa tengo una función que hace una simple suma de vectores c[0:15] = a[0:15] + b[0:15]
. El prototipo de función es:
void vecadd(float * restrict a, float * restrict b, float * restrict c);
En nuestra arquitectura integrada de 32 bits hay una opción de carga/almacenamiento para cargar/almacenar palabras dobles, como:
r16 = 0x4000 ;
strd r0,[r16] ; stores r0 in [0x4000] and r1 in [0x4004]
El optimizador GCC reconoce la naturaleza vectorial del bucle y genera dos ramas del código: una para el caso en que las 3 matrices están alineadas con palabras dobles (por lo que utiliza las instrucciones de carga/almacenamiento dobles) y la otra para el caso en que las matrices están alineados con palabras (donde usa la opción de carga/almacenamiento único).
El problema es que la verificación de alineación de direcciones es costosa en relación con la parte de suma y quiero eliminarla insinuando al compilador que a, b y c siempre están alineados en 8. ¿Hay algún modificador para agregar a la declaración del puntero para decirle esto al compilador?
Las matrices que se utilizan para llamar a esta función tienen el atributo alineado (8), pero no se refleja en el código de la función en sí. ¿Es posible agregar este atributo a los parámetros de la función?

ams
Si los atributos no funcionan, o no son una opción….
No estoy seguro, pero prueba esto:
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 ....
Eso debería decirle a GCC que los punteros están alineados. A partir de eso, si hace lo que quiere depende de si el compilador puede usar esa información de manera efectiva; puede que no sea lo suficientemente inteligente: estas optimizaciones no son fáciles.
Otra opción podría ser envolver el flotador dentro de una unión que debe estar alineada en 8 bytes:
typedef union {
float f;
long long dummy;
} aligned_float;
void vedadd (aligned_float * a, ......
Creo que eso debería hacer cumplir la alineación de 8 bytes, pero nuevamente, no sé si el compilador es lo suficientemente inteligente como para usarlo.

ysap
Siguiendo un fragmento de código de ejemplo que encontré en mi sistema, probé la siguiente solución, que incorpora ideas de algunas de las respuestas dadas anteriormente: básicamente, crear una unión de una pequeña matriz de flotadores con un tipo de 64 bits: en este caso, un vector SIMD de flotadores, y llame a la función con una conversión de las matrices de flotadores de operandos:
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;
}
Ahora el compilador no genera la rama alineada en 4.
sin embargo, el __builtin_assume_aligned()
sería la solución preferible, previniendo el yeso y posibles efectos secundarios, si tan solo funcionara…
EDITAR: noté que la función incorporada tiene errores en nuestra implementación (es decir, no solo no funciona, sino que causa errores de cálculo más adelante en el código).
¿Cómo decirle a GCC que un argumento de puntero siempre está alineado con dos palabras?
Parece que las versiones más nuevas de GCC tienen __builtin_assume_aligned
:
Función incorporada: void * __builtin_assume_aligned (const void *exp, size_t align, ...)
Esta función devuelve su primer argumento y permite que el compilador asuma que el puntero devuelto tiene al menos align bytes alineados. Este incorporado puede tener dos o tres argumentos, si tiene tres, el tercer argumento debe ser de tipo entero, y si es distinto de cero significa compensación de desalineación. Por ejemplo:
void *x = __builtin_assume_aligned (arg, 16);
significa que el compilador puede suponer que x, establecido en arg, tiene una alineación de al menos 16 bytes, mientras que:
void *x = __builtin_assume_aligned (arg, 32, 8);
significa que el compilador puede suponer para x, establecido en arg, que (char *) x – 8 está alineado en 32 bytes.
Según algunas otras preguntas y respuestas sobre Stack Overflow alrededor de 2010, parece que el integrado no estaba disponible en GCC 3 y GCC 4 temprano. Pero no sé dónde está el punto de corte.
Las versiones de gcc han sido dudosas acerca de align() en matrices y definiciones de tipos simples. Por lo general, para hacer lo que desea, tendría que envolver el flotador en una estructura y hacer que el flotador contenido tenga la restricción de alineación.
Con la sobrecarga de operadores, casi puede hacer que esto sea sencillo, pero se supone que puede usar la sintaxis de 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;
}
Las especificaciones de alineación generalmente solo funcionan para alineaciones que son más pequeñas que el tipo base de un puntero, no más grandes.
Creo que lo más fácil es declarar toda la matriz con una especificación de alineación, algo así como
typedef float myvector[16];
typedef myvector alignedVector __attribute__((aligned (8));
(La sintaxis puede no ser correcta, siempre tengo dificultades para saber dónde poner estos __attribute__
s)
Y use ese tipo en todo su código. Para su definición de función, intentaría
void vecadd(alignedVector * restrict a, alignedVector * restrict b, alignedVector * restrict c);
Esto le da una indirección adicional, pero esto es solo sintaxis. Algo como *a
es solo un noop y solo reinterpreta el puntero como un puntero al primer elemento.

Jörg Beyer
Nunca lo usé, pero hay _atributo_((alineado (8)))
Si leo bien la documentación, entonces se usa de esta manera:
void vecadd(float * restrict a __attribute__((aligned (8))),
float * restrict b __attribute__((aligned (8))),
float * restrict c __attribute__((aligned (8))));
ver http://ohse.de/uwe/articles/gcc-attributes.html#type-aligned
Incluso si mi código a continuación no puede ayudarlo (debido a que es C ++), es posible que desee imprimir f (“% p”) & matriz[0] y &matriz[1] en su código solo para asegurarse de que se obedezca la alineación, y por elemento, no solo en la dirección de inicio de la matriz.
– José
07/03/2012 a las 20:50
@Joe: en realidad se requiere que NO se alinee por elemento de matriz. Realmente tiene que ser una matriz contigua de flotantes, cuyo origen esté alineado en 8.
– ysap
7 de marzo de 2012 a las 21:48