2015-11-24 3 views
9

__m128の値で動作するコードがあります。私はこれらの値にx86-64 SSE組み込み関数を使用しています。値がメモリ内でアライメントされていないとクラッシュします。これは私のコンパイラ(この例ではclang)が整列したロード命令だけを生成するためです。コンパイラに__m128のアライメントされていないロードを生成させる方法

グローバルに、または特定の値(おそらく種類のアノテーションを含む)の代わりに、アライメントされていないロードを生成するようにコンパイラに指示できますか?


なぜなら、最初に値の境界が整列していないのは、メモリを節約しようとしているからです。私は、これらの構造体の配列を作成してい

#pragma pack(push, 4) 
struct Foobar { 
    __m128 a; 
    __m128 b; 
    int c; 
}; 
#pragma pack(pop) 

次のように私は大体structを持っています。配列の2番目の要素は36バイトから始まり、16の倍数ではありません。

私は配列表現の構造に切り替えることができ、またはパッキングプラグマを削除することができます。 36バイトから48バイトの構造体)。しかし、私はまた、アライメントの合っていない負荷は、最近は高価ではないことを知って、それを最初に試してみたいと思います。

inline Vector4 add(const Vector4& a, const Vector4 &b) { 
    return Vector4(_mm_add_ps(a.data, b.data)); 
} 

inline Vector4 subtract(const Vector4& a, const Vector4& b) { 
    return Vector4(_mm_sub_ps(a.data, b.data)); 
} 

// etc.. 

struct Vector4 { 
    __m128 data; 
    Vector4(__m128 v) : data(v) {} 
}; 
struct Foobar { 
    Vector4 a; 
    Vector4 b; 
    int c; 
} 

私は、その後のようないくつかのユーティリティ機能を持っている:

私の実際のコードはこれに近かった:


アップデートは以下のコメントのいくつかに答えるために

私はこれらのユーティリティを頻繁に組み合わせて使用​​します。フェイク例:「Z Bozon」さんを見ているときに私のコードは、効果的に変更答える

Foobar myArray[1000]; 
myArray[i+1].b = sub(add(myArray[i].a, myArray[i].b), myArray[i+1].a); 

struct Vector4 { 
    float data[4]; 
}; 

inline Vector4 add(const Vector4& a, const Vector4 &b) { 
    Vector4 result; 
    _mm_storeu_ps(result.data, _mm_add_ps(_mm_loadu_ps(a.data), _mm_loadu_ps(b.data))); 
    return result; 
} 

私の懸念があることユーティリティ機能は、上記のように組み合わせて使用​​したとき、ということでした生成されたコードに冗長なロード/ストア命令がある可能性があります。それは問題ではないことが判明しました。私はコンパイラ(clang)をテストし、それらをすべて削除しました。私はZ Bozonの答えを受け入れます。

+4

構造体に '__m128'を使用しないでください。例えば ​​'float a [4]'を使い、明示的に '_mm_loadu_ps'と' _mm_storeu_ps'を使ってロードとストアを行います。 –

+0

OPは明示的な組み込み関数を使用するだけでなく、場合によっては自動ベクトル化のためにclangによって生成されたSIMDコードを取得するように聞こえますか? –

+0

@PaulRの場合、OPはその情報を彼の質問に加えるべきです。 –

答えて

3

私の意見では、あなたのデータ構造は、標準のC++構造(そのうちの__m128iはありません)を使って書くべきです。標準C++でない組み込み関数を使用する場合は、_mm_loadu_psなどの組み込み関数を使用して「SSEワールド」に入れ、_mm_storeu_psなどの組み込み関数を使用して「SSEワールド」を標準C++に戻します。暗黙のSSEのロードとストアに依存しないでください。私はこれをやっているので、あまりにも多くの間違いを見てきました。あなたは

struct Foobar { 
    float a[4]; 
    float b[4]; 
    int c; 
}; 

を使用する必要があります。この場合

、あなたはこの場合

Foobar foo[16]; 

を行うことができますfoo[1]は整列16バイトではありませんが、あなたはSSEを使用すると、標準Cのままにしておきたいとき++ do

__m128 a4 = _mm_loadu_ps(foo[1].a); 
__m128 b4 = _mm_loadu_ps(foo[1].b); 
__m128 max = _mm_max_ps(a4,b4); 
_mm_storeu_ps(array, max); 

次に標準C++に戻ります。あなたが考えることができ

もう一つは、これは、元の構造体の16の配列を取得し、その後

struct Foobar { 
    float a[16]; 
    float b[16]; 
    int c[4]; 
}; 

である限り、最初の要素がそのようにすべてある整列される。この場合

Foobar foo[4]; 

を行います他の要素。


SSEレジスタで動作するユーティリティ機能を使用する場合は、ユーティリティ機能に明示的または暗黙的なロード/ストアを使用しないでください。必要に応じてconst参照を__m128に渡し、__m128を返します。

const参照を使用する理由は、MSVCが値で__m128を渡すことができないためです。 const参照がなければ、エラーが発生します。

エラーC2719:__declspec(整列('16 '))の仮パラメータは整列されません。

__m128(MSVC用)は、とにかく本当に組合です。

typedef union __declspec(intrin_type) _CRT_ALIGN(16) __m128 { 
    float    m128_f32[4]; 
    unsigned __int64 m128_u64[2]; 
    __int8    m128_i8[16]; 
    __int16    m128_i16[8]; 
    __int32    m128_i32[4]; 
    __int64    m128_i64[2]; 
    unsigned __int8  m128_u8[16]; 
    unsigned __int16 m128_u16[8]; 
    unsigned __int32 m128_u32[4]; 
} __m128; 

おそらくMSVCは、SSEユーティリティー関数がインライン化されているときに、共用体をロードする必要はありません。ここでOPは最新のコードの更新に基づいて


は、私はこのコードはAgner霧のVector Class Libraryからのアイデアに基づいていた

#include <x86intrin.h> 
struct Vector4 { 
    __m128 data; 
    Vector4() { 
    } 
    Vector4(__m128 const &v) { 
     data = v; 
    } 
    Vector4 & load(float const *x) { 
     data = _mm_loadu_ps(x); 
     return *this; 
    } 
    void store(float *x) const { 
     _mm_storeu_ps(x, data); 
    } 
    operator __m128() const { 
     return data; 
    } 
}; 

static inline Vector4 operator + (Vector4 const & a, Vector4 const & b) { 
    return _mm_add_ps(a, b); 
} 

static inline Vector4 operator - (Vector4 const & a, Vector4 const & b) { 
    return _mm_sub_ps(a, b); 
} 

struct Foobar { 
    float a[4]; 
    float b[4]; 
    int c; 
}; 

int main(void) 
{ 
    Foobar myArray[10]; 
    // note that myArray[0].a, myArray[0].b, and myArray[1].b should be  // initialized before doing the following 
    Vector4 a0 = Vector4().load(myArray[0].a); 
    Vector4 b0 = Vector4().load(myArray[0].b); 
    Vector4 a1 = Vector4().load(myArray[1].a);   
    (a0 + b0 - a1).store(myArray[1].b); 
} 

を示唆しているものです。

+0

よく書かれた応答をありがとう。これは私が上記のコメントに入れた質問の繰り返しですが、私は1つの点を明確にしたいと思います。 私が提案した方法を使用すると、それぞれがこのようにSSE世界を「入り」そして「残す」いくつかのユーティリティ機能を持ちます。これらの関数を連鎖すると、コンパイラは一般的に冗長なstoreu/loadu演算を削除するほどスマートになりますか? また、SSEの世界に1回だけ出入りする間に、いくつかのことを連続して実行する「事前結合」ユーティリティ関数を記述しなければならないことがわかりますか? – pauldoo

+0

@pauldoo、あなたはあなたの質問に何を意味するのかの例を追加できますか? –

+0

@pauldoo、SSEレジスタにデータを入力すると、それを関数に渡すことができます。 '__m128 foo(__ m128 const&a)'です。 MSVCを幸せにするためにconst参照を使用してください。 –

0

あなたはあなたに構造体を変更してみてください。もちろん、同じ大きさを持っているでしょう

#pragma pack(push, 4) 
struct Foobar { 
    int c; 
    __m128 a; 
    __m128 b; 
}; 
#pragma pack(pop) 

、および理論力打ち鳴らすに整列していないロード/ストアを生成する必要があります。


また、明示的なアライメントされていないロード/ストアを使用することもできます。変更:

v = _mm_max_ps(myArray[300].a, myArray[301].a) 

へ:あなたは自動ベクトル化または明示的なOpenMP4 /インテル®Cilk™/プラグマ駆動型ベクトル化を使用する場合は、あなたが使用してベクトル化されたループのために整列されていない負荷を使用するようにコンパイラに強制することができ

__m128i v1 = _mm_loadu_ps((float *)&myArray[300].a); 
__m128i v2 = _mm_loadu_ps((float *)&myArray[301].a); 
v = _mm_max_ps(v1, v2); 
+0

あなたの代わりの提案として、OPは 'float a [4]'を使用することもできます。 –

+0

True - 私は現在の構造体定義のイラストを追加していましたが、浮動小数点数の配列に切り替えるほうが意味があり、醜いキャストも取り除くことになります。 –

0

#pragma vector unaligned //for C/C++ 

CDEC$ vector unaligned ; for Fortran 

これは主に、「剥離が、整列していないではない」対「整列が、剥離」の間のトレードオフを制御することを意図しています。詳細は、https://software.intel.com/en-us/articles/utilizing-full-vectors

です。これは私が知る限り、インテルコンパイラでのみ有効です。インテル®コンパイラーは、コンパイル単位全体についても同様の処理を行うために、内部コンパイル・スイッチ-mP2OPT_vec_alignment = 6を使用します。

組み込み関数/アセンブリがOpenMP/Cilkと共に使用される実装に効果的に適用できるかどうかはチェックしませんでした。

関連する問題