SSEを使用してstd::vector
のデータを処理する場合は、16バイトのアライメントが必要です。どうすればそれを達成できますか?独自のアロケーターを作成する必要がありますか?または、デフォルトのアロケーターはすでに16バイト境界に整列していますか?
C++標準では、割り当て関数(malloc()
およびoperator new()
)を使用して、任意のstandard型に適切に配置されたメモリを割り当てます。これらの関数はアライメント要件を引数として受け取らないため、実際には、すべての割り当てのアライメントは同じであり、最大のアライメント要件を持つ標準タイプのアライメントであり、通常は_long double
_および/または_long long
_( boost max_align union を参照)。
SSEおよびAVXなどのベクトル命令には、標準のC++で提供されるものよりも強力なアライメント要件(128ビットアクセスでは16バイトアライメント、256ビットアクセスでは32バイトアライメント)があります。割り当て関数posix_memalign()
またはmemalign()
を使用して、より強いアライメント要件でこのような割り当てを満たすことができます。
vector
などの_std::
_コンテナではカスタムアロケータを使用する必要があります。次のコードを書いた人は覚えていませんが、しばらく使ってみましたが、機能しているようです(コンパイラ/プラットフォームによっては、__aligned_malloc
_を__mm_malloc
_に変更する必要がある場合があります)。
_#ifndef ALIGNMENT_ALLOCATOR_H
#define ALIGNMENT_ALLOCATOR_H
#include <stdlib.h>
#include <malloc.h>
template <typename T, std::size_t N = 16>
class AlignmentAllocator {
public:
typedef T value_type;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef T * pointer;
typedef const T * const_pointer;
typedef T & reference;
typedef const T & const_reference;
public:
inline AlignmentAllocator () throw () { }
template <typename T2>
inline AlignmentAllocator (const AlignmentAllocator<T2, N> &) throw () { }
inline ~AlignmentAllocator () throw () { }
inline pointer adress (reference r) {
return &r;
}
inline const_pointer adress (const_reference r) const {
return &r;
}
inline pointer allocate (size_type n) {
return (pointer)_aligned_malloc(n*sizeof(value_type), N);
}
inline void deallocate (pointer p, size_type) {
_aligned_free (p);
}
inline void construct (pointer p, const value_type & wert) {
new (p) value_type (wert);
}
inline void destroy (pointer p) {
p->~value_type ();
}
inline size_type max_size () const throw () {
return size_type (-1) / sizeof (value_type);
}
template <typename T2>
struct rebind {
typedef AlignmentAllocator<T2, N> other;
};
bool operator!=(const AlignmentAllocator<T,N>& other) const {
return !(*this == other);
}
// Returns true if and only if storage allocated from *this
// can be deallocated from other, and vice versa.
// Always returns true for stateless allocators.
bool operator==(const AlignmentAllocator<T,N>& other) const {
return true;
}
};
#endif
_
次のように使用します(必要に応じて、16を別の配置に変更します)。
_std::vector<T, AlignmentAllocator<T, 16> > bla;
_
ただし、これは、_std::vector
_が使用するメモリブロックが16バイト境界で整列されていることを確認するだけです。 sizeof(T)
が16の倍数でない場合、一部の要素は整列されません。データ型によっては、これは問題にならない場合があります。 T
がint
(4バイト)の場合、インデックスが4の倍数である要素のみをロードします。それがdouble
(8バイト)の場合、2の倍数のみなどです。
実際の問題は、クラスをT
として使用する場合です。この場合、クラス自体でアライメント要件を指定する必要があります(ここでも、コンパイラによっては、これは異なる場合があります。例はGCCの場合です):
_class __attribute__ ((aligned (16))) Foo {
__attribute__ ((aligned (16))) double u[2];
};
_
あと少しで完了です。 Visual C++(少なくともバージョン2010)を使用する場合、_std::vector
_のため、指定した配置のクラスで_std::vector::resize
_を使用することはできません。
コンパイル時に次のエラーが発生した場合:
_C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector(870):
error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned
_
_stl::vector header
_ファイルをハックする必要があります。
vector
ヘッダーファイルを見つけます[C:\ Program Files\Microsoft Visual Studio 10.0\VC\include\vector]void resize( _Ty _Val )
メソッドを見つけます[VC2010の行870]void resize( const _Ty& _Val )
に変更します。独自のアロケータを作成する代わりに、 前に提案 のように、 boost::alignment::aligned_allocator
std::vector
このような:
#include <vector>
#include <boost/align/aligned_allocator.hpp>
template <typename T>
using aligned_vector = std::vector<T, boost::alignment::aligned_allocator<T, 16>>;
sizeof(T)*vector.size() > 16
の場合は、はい。
ベクターが通常のアロケーターを使用すると仮定します
警告:alignof(std::max_align_t) >= 16
が最大の配置である限り。
2017年8月25日更新された新しい標準 n4659
16より大きいものに対して配置されている場合は、16に対しても正しく配置されています。
アライメントは、std :: size_t型の値として表されます。有効な配置には、基本タイプのalignof式によって返される値と、実装で定義された追加の値のセットが含まれます。これらは空でもかまいません。すべてのアライメント値は、2の負でない整数の累乗でなければなりません。
整列には、弱い整列から強い整列または厳密な整列への順序があります。厳密な配置では、配置値が大きくなります。アラインメント要件を満たすアドレスは、より弱い有効なアラインメント要件も満たします。
newおよびnew []は、オブジェクトがそのサイズに対して正しく配置されるように配置された値を返します。
[注:割り当て関数がnull以外の値を返す場合、それはオブジェクトのスペースが予約されているストレージのブロックへのポインターでなければなりません。ストレージのブロックは、適切に調整され、要求されたサイズであると想定されます。オブジェクトが配列の場合、作成されたオブジェクトのアドレスは、必ずしもブロックのアドレスと同じになるとは限りません。 —エンドノート]
ほとんどのシステムには最大の調整があることに注意してください。動的に割り当てられたメモリは、これより大きい値に調整する必要はありません。
基本的な配置は、すべてのコンテキストの実装でサポートされている最大の配置以下の配置で表されます。これは、alignof(std :: max_align_t)(21.2)と同じです。完全なオブジェクトのタイプとして使用される場合とサブオブジェクトのタイプとして使用される場合、タイプに必要な配置は異なる場合があります。
したがって、割り当てられたベクトルメモリが16バイトを超える限り、16バイト境界に正しく配置されます。
独自のアロケータを記述します。 allocate
とdeallocate
は重要なものです。以下はその一例です。
pointer allocate( size_type size, const void * pBuff = 0 )
{
char * p;
int difference;
if( size > ( INT_MAX - 16 ) )
return NULL;
p = (char*)malloc( size + 16 );
if( !p )
return NULL;
difference = ( (-(int)p - 1 ) & 15 ) + 1;
p += difference;
p[ -1 ] = (char)difference;
return (T*)p;
}
void deallocate( pointer p, size_type num )
{
char * pBuffer = (char*)p;
free( (void*)(((char*)p) - pBuffer[ -1 ] ) );
}
Intelのベクトル化チュートリアルで説明されているようにdeclspec(align(x,y))
を使用します http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf