私はSSE/SSE2命令を使用してコードを最適化するのが初めてで、今までのところ、それほど遠くまでは行きません。私の知る限り、一般的なSSE最適化関数は次のようになります。
void sse_func(const float* const ptr, int len){
if( ptr is aligned )
{
for( ... ){
// unroll loop by 4 or 2 elements
}
for( ....){
// handle the rest
// (non-optimized code)
}
} else {
for( ....){
// regular C code to handle non-aligned memory
}
}
}
ただし、メモリptr
が指しているメモリが、たとえば16バイト?この関数に渡されるすべてのメモリが整列されることを確認できないため、非整列メモリの通常のCコードパスを含める必要があると思います。そして、組み込み関数を使用して、アライメントされていないメモリからSSEレジスタにデータをロードすることは、恐ろしく遅いようです(通常のCコードよりも遅いようです)。
前もって感謝します...
編集:long
へのキャストは、intとポインターが現在異なるサイズである可能性が最も高い可能性から自分を保護する安価な方法です。
以下のコメントで指摘されているように、ヘッダーを含めても構わない場合は、より良い解決策があります...
ポインターp
は、((unsigned long)p & 15) == 0
。
#define is_aligned(POINTER, BYTE_COUNT) \
(((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)
規格ではvoid *
のchar *
への可逆変換のみを保証しているため、uintptr_t
(または同等のvoid *
)へのキャストが必要です。
タイプセーフが必要な場合は、インライン関数の使用を検討してください。
static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }
byte_count
がコンパイル時の定数である場合は、コンパイラの最適化を期待してください。
変換する必要がある理由void *
?
C言語では、ポインタータイプごとに異なる表現を使用できます。たとえば、64ビットのvoid *
タイプ(アドレス空間全体)と32ビットのfoo *
タイプ(セグメント)を使用できます。
変換foo *
-> void *
には、オフセットの追加など、実際の計算が含まれる場合があります。標準はまた、(任意の)ポインターを整数に変換するときに何が起こるかを実装に任せていますが、私はそれがnoopとして実装されることが多いと思います。
そのような実装の場合、foo *
-> uintptr_t
-> foo *
は機能しますが、foo *
-> uintptr_t
-> void *
およびvoid *
-> uintptr_t
-> foo *
はそうしません。セグメントオフセットに関連するアライメントのみをチェックするため、アライメント計算も確実に機能しません。
結論:常にvoid *
を使用して、実装に依存しない動作を実現します。
他の答えは、下位ビットが設定されたAND演算を提案し、ゼロと比較します。
ただし、より簡単なテストは、目的のアライメント値でMODを実行し、ゼロと比較することです。
#define ALIGNMENT_VALUE 16u
if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
// ptr is aligned
}
のような関数テンプレートで
#include <type_traits>
template< typename T >
bool is_aligned(T* p){
return !(reinterpret_cast<uintptr_t>(p) % std::alignment_of<T>::value);
}
あなたは次のようなものを呼び出すことによって実行時にアライメントをチェックすることができます
struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes
悪いアライメントが失敗することを確認するには、次のようにすることができます
// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
これは基本的に私が使用しているものです。整数をテンプレートにすることで、コンパイル時の拡張を確実にするため、私が何をしても、モジュロ演算が遅くなることはありません。
私はいつも入力をチェックするのが好きなので、コンパイル時のアサーションを行います。アライメント値が間違っていると、コンパイルされません...
template <unsigned int alignment>
struct IsAligned
{
static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");
static inline bool Value(const void * ptr)
{
return (((uintptr_t)ptr) & (alignment - 1)) == 0;
}
};
何が起こっているかを確認するには、これを使用できます:
// 1 of them is aligned...
int* ptr = new int[8];
for (int i = 0; i < 8; ++i)
std::cout << IsAligned<32>::Value(ptr + i) << std::endl;
// Should give '1'
int* ptr2 = (int*)_aligned_malloc(32, 32);
std::cout << IsAligned<32>::Value(ptr2) << std::endl;
それは専門家に任せて、
bool is_aligned(const void* ptr, std::size_t alignment) noexcept;
例:
char D[1];
assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); // might fail, sometimes
最下位ビットのいずれかが設定されているかどうかを確認するために、ptrを0x03(4sにアライン)、0x07(8sにアライン)、または0x0f(16sにアライン)で「と」できますか?