46

私は 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 コードよりもさらに遅い)。

前もって感謝します...

4

8 に答える 8

54
#define is_aligned(POINTER, BYTE_COUNT) \
    (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)

標準では for への可逆変換のみが保証されているためvoid *、 (または同等の)へのキャストが必要です。char *uintptr_tvoid *

タイプ セーフが必要な場合は、インライン関数の使用を検討してください。

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 *結論として、実装に依存しない動作を得るために常に使用してください。

于 2009-12-14T01:26:57.593 に答える
32

編集: へのキャストlongは、最近 int とポインターが異なるサイズになる可能性が最も高い可能性から身を守るための安価な方法です。

以下のコメントで指摘されているように、ヘッダーを含める意思がある場合は、より良い解決策があります...

ポインターpは 16 バイト境界 iff に位置合わせされます((unsigned long)p & 15) == 0

于 2009-12-13T23:16:58.697 に答える
25

他の回答は、下位ビットが設定された AND 演算とゼロとの比較を示唆しています。

しかし、より簡単なテストは、目的のアラインメント値で MOD を実行し、ゼロと比較することです。

#define ALIGNMENT_VALUE     16u

if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
    // ptr is aligned
}
于 2009-12-13T23:27:43.280 に答える
9

次のような関数テンプレートを使用

#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)));
于 2015-02-23T16:37:18.283 に答える
6

これは基本的に私が使用しているものです。整数をテンプレートにすることで、コンパイル時間が確実に長くなるため、何をしてもモジュロ演算が遅くなることがありません。

私はいつも自分の入力をチェックするのが好きなので、コンパイル時のアサーションです。アラインメントの値が間違っていると、コンパイルできません...

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;
于 2015-02-27T08:03:44.750 に答える
2

ptr を 0x03 (4 秒に整列)、0x07 (8 秒に整列)、または 0x0f (16 秒に整列) と「AND」して、最下位ビットのいずれかが設定されているかどうかを確認できますか?

于 2009-12-13T23:17:04.970 に答える
-3

どうですか:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
于 2012-09-04T08:52:01.370 に答える