2017-04-27 2 views
23

Hier einige Code, der GCC 6 und 7 nicht zur Optimierung zu optimieren schlägt fehl, wenn std::array mit:GCC ausgerichtet std :: Array wie C-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]; 
    } 
} 

die oben Kompilieren mit g++ -std=c++14 -O3 -march=haswell -S -DC_ARRAY produziert schöne Code:

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 

, die über 256-Bit-Register zu einem Zeitpunkt der Zugabe von vier Doppel grundsätzlich zwei abgerollt Iterationen ist. Aber wenn Sie ohne -DC_ARRAY kompilieren, erhalten Sie ein großes Durcheinander mit dieser Start:

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

Der Code erzeugt in diesem Fall (std::array anstelle eines einfachen C-Array verwendet wird) für die Ausrichtung der Eingabe zu überprüfen scheint array-- obwohl es in typedef als auf 32 Bytes ausgerichtet angegeben ist.

Es scheint, dass GCC nicht versteht, dass der Inhalt eines die gleichen wie die selbst ausgerichtet sind. Dies unterbindet die Annahme, dass die Verwendung von anstelle von C-Arrays keine Laufzeitkosten verursacht.

Gibt es etwas, einfach fehlt mir, dass dieses Problem beheben würde? Bisher habe ich mit einem hässlichen Hack kam:

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]; 
    } 
} 

Beachten Sie auch: wenn my_elements 4 statt 8, tritt das Problem nicht auf. Wenn Sie Clang verwenden, tritt das Problem nicht auf.

Sie können es hier live sehen: https://godbolt.org/g/IXIOst

+4

FWIW, Klirren beklagt, dass 'alignas' auf einem Datum Mitglied sein muss, nicht auf einem typedef, aber wenn Wechsel' Vec' zu einer geschachtelten Klasse, die 'std :: array <...>' als ein ausgerichteter Datenmember hält und ihm 'operator []' overloads gibt, dann kann clang das optimieren. GCC tut es immer noch nicht. – hvd

+3

Hat das dem 'std :: array' zugrunde liegende Array die gleiche Ausrichtung wie das' std :: array'? –

+1

Und wenn 'Vec' als eine Klasse implementiert wird, die' double data [my_elements] alignas (32); ', mit benutzerdefiniertem' operator [] 'enthält, dann kann GCC dies optimieren. Ich vermute, das Problem ist, dass 'array :: operator []' ein nicht ausgerichtetes 'double &' zurückgibt, das von seinem nicht ausgerichteten 'array :: _ M_elems' Element kommt, und die Tatsache, dass es Teil eines ausgerichteten Arrays ist, ist nur ein bisschen zu weit damit der Optimierer es sehen kann. – hvd

Antwort

17

Interessanterweise, wenn Sie v1[i] += v2a[i]; mit v1._M_elems[i] += v2._M_elems[i]; ersetzen (was natürlich nicht tragbar ist), gcc verwaltet die std :: array Fall sowie bei der C zu optimieren Array.

Mögliche Interpretation: in den gcc-Dumps (-fdump-tree-all-all), kann man MEM[(struct FooD.25826 *)this_7(D) clique 1 base 0].v1D.25832[i_15] im C-Fall und MEM[(const value_typeD.25834 &)v2_7(D) clique 1 base 1][_1] für std :: array sehen. Das heißt, im zweiten Fall hat gcc möglicherweise vergessen, dass dies Teil des Typs Foo ist und nur daran erinnert, dass es auf ein Double zugreift.

Dies ist eine Abstraktion Strafe, die von allen Inline-Funktionen kommt man durch die Array-Zugriffs zu sehen, endlich zu gehen. Clang schafft es immer noch gut zu vektorisieren (selbst nach dem Entfernen von Alignas!). Dies bedeutet wahrscheinlich, dass der Clam vektorisiert, ohne sich um die Ausrichtung zu kümmern, und tatsächlich verwendet er Anweisungen wie vmovupd, die keine ausgerichtete Adresse erfordern.

Der Hack Sie, zu Vec Gießen, ist eine andere Art und Weise der Compiler sehen zu lassen, wenn sie den Speicherzugriff behandelt, dass die Art ausgerichtet wird behandelt. Für ein reguläres Array std :: :: operator [], passiert der Speicherzugriff innerhalb einer Elementfunktion std :: Array, das keine Ahnung hat, dass *this ausgerichtet werden, geschieht.

Gcc auch ein eingebautes hat den Compiler weiß Ausrichtung zu lassen:

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

Ich habe es an GCC hier gemeldet: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80561 –

+3

Vielen Dank für die Einreichung der Fehlerbericht :-) –