2017-04-27 11 views
23

ような配列は、ここではGCC 6と7はstd::arrayを使用するときに最適化することができないいくつかのコードです:g++ -std=c++14 -O3 -march=haswell -S -DC_ARRAYと上記のコンパイルGCCが整列STDを最適化するために失敗した:: C配列

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

は、素敵なコードを生成します:

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 

これは、基本的に、256ビットレジスタを介して一度に4つの倍精度を追加する2つの展開されていない反復です。しかし、あなたは-DC_ARRAYせずにコンパイルした場合、あなたはこの始まる巨大な混乱を取得:

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

std::arrayの代わりに、プレーンなC配列を使用して)、この場合に生成されたコードは、入力のアライメントをチェックするようですarray-- 32バイトに整列されたtypedefで指定されています。

std::arrayの内容がstd::arrayと同じになっているとGCCは理解していないようです。これは、C配列の代わりにstd::arrayを使用するとランタイムコストが発生しないという前提を壊します。

この問題を解決する簡単な方法がありますか?これまでのところ私は醜いハックを思い付いた:

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

も注意してください。my_elementsは4ではなく8であれば、問題は発生しません。 Clangを使用すると、問題は発生しません。 、GCCは、Cの場合と同様のstd ::配列のケースを最適化するために管理しhttps://godbolt.org/g/IXIOst

+4

FWIW、打ち鳴らすは 'alignas'は、データメンバに、ではないのtypedefであることが必要と文句を言いますが、' Vec'を変更する場合'std :: array <...>'を整列したデータメンバとして保持し、それに 'operator []'オーバーロードを与えるネストされたクラスに渡すと、clangはこれを最適化するために管理します。 GCCは依然としてそうではありません。 – hvd

+3

'std :: array'の基礎となる配列は、' std :: array'と同じ配列を持っていますか? –

+1

'' Vec'がカスタム 'operator []'で 'double data [my_elements] alignas(32);'を保持するクラスとして実装されている場合、GCCはこれを最適化するために管理します。私は問題が 'array :: operator []'がその配列されていない 'array :: _ M_elems'メンバーから来るアラインメントされていない' double 'を返すということであり、アライメントされた配列の一部であるという事実は、オプティマイザがそれを見ることができるようにします。 – hvd

答えて

17

を興味深いことに、あなたは(明らかに移植されていない)v1._M_elems[i] += v2._M_elems[i];v1[i] += v2a[i];を交換する場合:

あなたはそれがここに住んで見ることができますアレイ。

考えられる解釈:gccダンプ(-fdump-tree-all-all)では、C配列の場合はMEM[(struct FooD.25826 *)this_7(D) clique 1 base 0].v1D.25832[i_15]、std :: arrayの場合はMEM[(const value_typeD.25834 &)v2_7(D) clique 1 base 1][_1]と表示されます。つまり、2番目のケースでは、gccはこれがFoo型の一部であり、doubleにアクセスしていることを忘れている可能性があります。

これは最終的にアレイへのアクセスを見るために必要なすべてのインライン関数から来る抽象的なペナルティです。 Clangは依然として(たとえalignasを削除した後でも)うまくベクトル化することができます。これは、アラインメントを気にすることなくclangがベクトル化し、実際にはアライメントされたアドレスを必要としないvmovupdのような命令を使用することを意味します。

Vecにキャストしたハックは、メモリアクセスを処理するときに処理される型が整列していることをコンパイラに見せるもう1つの方法です。通常のstd :: array :: operator []の場合、メモリアクセスはstd :: arrayのメンバ関数の内部で行われます。*thisが整列するという手掛かりはありません。

GCCはまた、コンパイラは、アライメントを知らせるための組み込みがあります。

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

こちらをGCCに報告しました:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80561 –

+3

バグレポートを提出してくれてありがとう:-) –

関連する問題