多くのSSEコンパイラ組み込み関数を使用して3Dベクトルクラスを作成しました。3Dベクトルを新しいメンバーとして持つクラスを開始するまで、すべてが正常に機能しました。リリースで奇妙なクラッシュが発生しました。モードですが、デバッグモードではありません。その逆も同様です。
そこで、いくつかの記事を読んで、3Dベクトルクラスのインスタンスを所有するクラスも16バイトに揃える必要があると考えました。だから私はちょうど次のようにクラスの前に__MM_ALIGN16
_(__declspec(align(16)
)を追加しました:
__MM_ALIGN16 struct Sphere
{
// ....
Vector3 point;
float radius
};
_
それで最初は問題が解決したようです。しかし、いくつかのコードを変更した後、私のプログラムは再び奇妙な方法でクラッシュし始めました。 Webをもう少し検索して、 blog の記事を見つけました。著者のErnstHotが問題を解決するために行ったことを試しましたが、それは私にも役立ちます。次のように、クラスにnew演算子とdelete演算子を追加しました。
__MM_ALIGN16 struct Sphere
{
// ....
void *operator new (unsigned int size)
{ return _mm_malloc(size, 16); }
void operator delete (void *p)
{ _mm_free(p); }
Vector3 point;
float radius
};
_
Ernstは、このアプローチも問題になる可能性があると述べていますが、問題になる理由を説明せずに、もう存在しないフォーラムにリンクしているだけです。
だから私の質問は:
演算子の定義の問題は何ですか?
クラス定義に__MM_ALIGN16
_を十分に追加できないのはなぜですか?
SSE組み込み関数に伴うアライメントの問題を処理するための最良の方法は何ですか?
まず、2種類のメモリ割り当てに注意する必要があります。
静的割り当て。自動変数を適切に整列させるには、タイプに適切な整列指定が必要です(例:__declspec(align(16))
、__attribute__((aligned(16)))
、または__MM_ALIGN16
_)。ただし、幸いなことに、これが必要になるのは、タイプのメンバー(存在する場合)によって指定された配置要件が十分でない場合のみです。したがって、_Vector3
_がすでに適切に配置されている場合、これはSphere
には必要ありません。また、_Vector3
_に___m128
_メンバーが含まれている場合(これは可能性が高いですが、そうでない場合はそうすることをお勧めします)、_Vector3
_には必要ありません。したがって、通常、コンパイラ固有のアライメント属性をいじる必要はありません。
動的割り当て。簡単な部分はこれだけです。問題は、C++が最下位レベルで、動的メモリを割り当てるために、タイプに依存しないメモリ割り当て関数を使用することです。これは、すべての標準タイプの適切な配置を保証するだけです。これは、たまたま16バイトである可能性がありますが、保証されていません。
これを補うには、組み込みの_operator new/delete
_をオーバーロードして独自のメモリ割り当てを実装し、古き良きmalloc
の代わりに内部で整列された割り当て関数を使用する必要があります。 _operator new/delete
_のオーバーロードはそれ自体がトピックですが、最初は(例では不十分ですが)見た目ほど難しくはなく、 この優れた_で読むことができます。FAQ質問 。
残念ながら、非標準の配置を必要とするメンバーがあるタイプごとにこれを行う必要があります。この場合、Sphere
と_Vector3
_の両方です。しかし、少し簡単にするためにできることは、それらの演算子に適切なオーバーロードを使用して空の基本クラスを作成し、この基本クラスから必要なすべてのクラスを派生させることです。
ほとんどの人が時々忘れがちなのは、標準のアロケータ_std::alocator
_がすべてのメモリ割り当てにグローバル_operator new
_を使用するため、タイプが標準のコンテナで機能しないことです(_std::vector<Vector3>
_は機能しません 'まれなユースケース)。あなたがする必要があるのは、あなた自身の標準的な適合アロケータを作り、これを使うことです。ただし、利便性と安全性のために、実際にはタイプに合わせて_std::allocator
_を特殊化することをお勧めします(カスタムアロケーターから派生するだけかもしれません)。これにより、常に使用され、それぞれ適切なアロケーターを使用する必要がなくなります。 _std::vector
_を使用するとき。残念ながら、この場合、整列されたタイプごとに再度特殊化する必要がありますが、小さな邪悪なマクロがそれを助けます。
さらに、_operator new/delete
_や_std::get_temporary_buffer
_など、カスタムではなくグローバル_std::return_temporary_buffer
_を使用して他のものを探し、必要に応じてそれらを処理する必要があります。
残念ながら、これらの問題に対するより良いアプローチはまだありません。ネイティブに16に対応するプラットフォームを使用している場合を除きますそしてこれについて知っています。または、グローバル_operator new/delete
_をオーバーロードして、各メモリブロックを常に16バイトに整列し、SSEメンバーを含むすべてのクラスの整列を気にしないようにすることもできますが、私はしません。このアプローチの意味について知っている。最悪の場合、メモリが無駄になるはずですが、通常はC++で小さなオブジェクトを動的に割り当てません(ただし、_std::list
_と_std::map
_はこれについて異なる考え方をする可能性があります)。
要約すると:
__declspec(align(16))
のようなものを使用して静的メモリの適切な配置に注意してください。ただし、それがまだメンバーによって管理されていない場合に限ります。これは通常の場合です。
非標準の配置要件を持つメンバーを持つすべてのタイプに対して_operator new/delete
_をオーバーロードします。
整列されたタイプの標準コンテナーで使用するための標準準拠のアロケーターを作成します。さらに、整列されたタイプごとに_std::allocator
_を特殊化します。
最後にいくつかの一般的なアドバイス。多くのベクトル演算を実行する場合、計算量の多いブロックでSSEからのみ利益を得ることがよくあります。このすべてのアラインメントの問題、特に_Vector3
_を含むすべてのタイプのアラインメントのケアの問題を単純化するには、特別なSSEベクトルタイプを作成してのみ使用することをお勧めします。これは、ストレージ変数とメンバー変数に通常の非SSEベクトルを使用した、長い計算の内部です。
基本的に、SIMDベクトルタイプには通常、どの組み込みタイプよりも大きな位置合わせ要件があるため、ベクトルが適切に位置合わせされていることを確認する必要があります。
それには、次のことを行う必要があります。
_Vector3
_がスタックまたは構造体のメンバー上にある場合は、正しく配置されていることを確認してください。これは、__attribute__((aligned(32)))
を_Vector3
_クラス(またはコンパイラでサポートされている属性)に適用することによって行われます。 _Vector3
_を含む構造に属性を適用する必要はないことに注意してください。これは必要ではなく、十分ではありません(つまり、Sphere
に適用する必要はありません)。
ヒープ割り当てを使用するときは、_Vector3
_またはその囲み構造が適切に配置されていることを確認してください。これは、プレーンなposix_memalign()
またはmalloc()
を使用する代わりに、operator new()
(またはプラットフォームの同様の関数)を使用して行われます。後者の2つは、組み込みのメモリを整列させるためです。 SIMDタイプに十分であるとは限らないタイプ(通常は8または16バイト)。
演算子の問題は、それ自体で十分ではないということです。スタックの割り当てには影響しませんが、__declspec(align(16))
が必要です。
__declspec(align(16))
は、選択できる場合に限り、コンパイラがオブジェクトをメモリに配置する方法に影響を与えます。新しいオブジェクトの場合、コンパイラはoperator new
によって返されたメモリを使用する以外に選択肢はありません。
理想的には、それらをネイティブに処理するコンパイラーを使用してください。 double
とは異なる方法で処理する必要がある理論的な理由はありません。それ以外の場合は、回避策についてコンパイラのドキュメントをお読みください。障害のあるコンパイラにはそれぞれ独自の問題があり、したがって独自の回避策があります。