una programa matriz matrices llenar funciones ejercicios dev constante con 2x2 c++ gcc optimization simd memory-alignment

c++ - programa - GCC no puede optimizar el std:: array alineado como la matriz C



matriz constante c++ (1)

Aquí hay algunos códigos que GCC 6 y 7 no pueden optimizar al usar std::array :

#include <array> static constexpr size_t my_elements = 8; class Foo { public: #ifdef C_ARRAY typedef double Vec[my_elements] alignas(32); #else typedef std::array<double, my_elements> Vec alignas(32); #endif void fun1(const Vec&); Vec v1{{}}; }; void Foo::fun1(const Vec& __restrict__ v2) { for (unsigned i = 0; i < my_elements; ++i) { v1[i] += v2[i]; } }

Compilando lo anterior con g++ -std=c++14 -O3 -march=haswell -S -DC_ARRAY produce un buen código:

vmovapd ymm0, YMMWORD PTR [rdi] vaddpd ymm0, ymm0, YMMWORD PTR [rsi] vmovapd YMMWORD PTR [rdi], ymm0 vmovapd ymm0, YMMWORD PTR [rdi+32] vaddpd ymm0, ymm0, YMMWORD PTR [rsi+32] vmovapd YMMWORD PTR [rdi+32], ymm0 vzeroupper

Eso es básicamente dos iteraciones desenrolladas de agregar cuatro dobles a la vez a través de registros de 256 bits. Pero si compilas sin -DC_ARRAY , obtienes un gran lío comenzando con esto:

mov rax, rdi shr rax, 3 neg rax and eax, 3 je .L7

El código generado en este caso (usando std::array lugar de una matriz C simple) parece verificar la alineación de la matriz de entrada, incluso si está especificado en typedef como alineado a 32 bytes.

Parece que GCC no comprende que los contenidos de una std::array estén alineados de la misma manera que la std::array misma. Esto rompe la suposición de que usar std::array lugar de C arrays no implica un costo en tiempo de ejecución.

¿Hay algo simple que me falta que solucione esto? Hasta ahora se me ocurrió un truco feo:

void Foo::fun2(const Vec& __restrict__ v2) { typedef double V2 alignas(Foo::Vec); const V2* v2a = static_cast<const V2*>(&v2[0]); for (unsigned i = 0; i < my_elements; ++i) { v1[i] += v2a[i]; } }

También tenga en cuenta: si my_elements es 4 en lugar de 8, el problema no ocurre. Si usa Clang, el problema no ocurre.

Puede verlo en vivo aquí: https://godbolt.org/g/IXIOst


Curiosamente, si reemplaza v1[i] += v2a[i]; con v1._M_elems[i] += v2._M_elems[i]; (que obviamente no es portátil), gcc logra optimizar el caso std :: array así como el caso de la matriz C.

Posible interpretación: en los volcados de gcc ( -fdump-tree-all-all ), se puede ver MEM[(struct FooD.25826 *)this_7(D) clique 1 base 0].v1D.25832[i_15] en la matriz C case, y MEM[(const value_typeD.25834 &)v2_7(D) clique 1 base 1][_1] para std :: array. Es decir, en el segundo caso, gcc puede haber olvidado que esto es parte del tipo Foo y solo recuerda que está accediendo a un doble.

Esta es una penalización de abstracción que proviene de todas las funciones en línea que uno tiene que atravesar para finalmente ver el acceso a la matriz. Clang todavía logra vectorizar muy bien (¡incluso después de eliminar las alignas!). Esto probablemente significa que clang vectoriza sin preocuparse por la alineación, y de hecho utiliza instrucciones como vmovupd que no requieren una dirección alineada.

El truco que encontraste, enviar a Vec, es otra forma de dejar que el compilador vea, cuando maneja el acceso a la memoria, que el tipo que se maneja está alineado. Para un ordinario std :: array :: operator [], el acceso a memoria ocurre dentro de una función miembro de std :: array, que no tiene ninguna pista de que *this está alineado.

Gcc también tiene incorporado para informar al compilador sobre la alineación:

const double*v2a=static_cast<const double*>(__builtin_assume_aligned(v2.data(),32));