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)

强制转换为void *(或等价的char *)是必要的,因为标准只保证可逆转换为uintptr_tfor 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 *用来获得独立于实现的行为。

于 2009-12-14T01:26:57.593 回答
32

编辑:铸造 tolong是一种廉价的方法,可以保护自己免受当今最可能出现的 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 回答
5

交给专业人士吧

https://www.boost.org/doc/libs/1_65_1/doc/html/align/reference.html#align.reference.functions.is_aligned

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
于 2019-07-08T20:10:24.900 回答
2

你能用 0x03(在 4s 上对齐)、0x07(在 8s 上对齐)或 0x0f(在 16s 上对齐)“和”ptr 来查看是否设置了任何最低位吗?

于 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 回答