如何确定内存是否对齐?

use*_*898 40 c memory optimization sse simd

我是使用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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如何正确确定内存ptr点是否与16字节对齐?我认为我必须包含非对齐内存的常规C代码路径,因为我无法确保传递给此函数的每个内存都将对齐.使用内在函数将数据从未对齐的内存加载到SSE寄存器似乎非常慢(甚至比常规C代码慢).

先感谢您...

Chr*_*oph 49

#define is_aligned(POINTER, BYTE_COUNT) \
    (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)
Run Code Online (Sandbox Code Playgroud)

转换为void *(或等效char *)是必要的,因为标准仅保证可逆转换uintptr_tvoid *.

如果您想要类型安全,请考虑使用内联函数:

static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }
Run Code Online (Sandbox Code Playgroud)

并希望编译器优化if byte_count是编译时常量.

为什么我们需要转换为 void *

C语言允许不同指针类型的不同表示,例如,您可以具有64位void *类型(整个地址空间)和32位foo *类型(段).

转换foo *- > void *可能涉及实际计算,例如添加偏移量.该标准还将实现在将(任意)指针转换为整数时发生的事情,但我怀疑它通常是作为noop实现的.

对于这样的实现,foo *- > uintptr_t- > foo *会起作用,但是foo *- > uintptr_t- > void *void *- > uintptr_t- > foo *不会.对齐计算也不能可靠地工作,因为您只检查相对于段偏移的对齐,这可能是您想要的,也可能不是.

总结:始终使用void *以实现与实现无关的行为.

  • 如果“float *”(理论上)可以具有与“void *”不同的表示形式,这是否意味着对齐检查可能发生在与预期不同的值上? (2认同)

Pas*_*uoq 27

编辑:铸造long是一种廉价的方式来保护自己免受int和指针现在不同大小的可能性.

正如下面的评论中所指出的,如果您愿意包含标题,那么有更好的解决方案......

指针p在16字节边界iff上对齐((unsigned long)p & 15) == 0.

  • 您可以改为使用`uintptr_t` - 保证指针的大小正确.当然,只要您的编译器定义它. (25认同)
  • 我通常会使用`p%16 == 0`,因为编译器通常知道2的幂,就像我一样,我发现它更具可读性 (15认同)
  • 如果指针和整数大小不匹配并不重要.你只关心最后几位. (8认同)
  • @Pascal Cuoq,gcc注意到了这一点,并为设置了-O标志的`(p&15)== 0`和`(p%16)== 0`发出了完全相同的代码。我已经看到许多其他编译器以2的幂识别整数除法/模数/乘法,并且对此做得很聪明。(我确实同意强制转换为未签名) (2认同)

Cra*_*een 22

其他答案建议设置低位的AND运算,并与零进行比较.

但更直接的测试是使用所需的对齐值进行MOD,并与零进行比较.

#define ALIGNMENT_VALUE     16u

if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
    // ptr is aligned
}
Run Code Online (Sandbox Code Playgroud)

  • 我赞成你,但只是因为你使用的是无符号整数:) (5认同)
  • @jww我不确定我明白你的意思。对齐要求为1基本上意味着没有对齐要求。无需担心uint8_t的对齐。但请澄清我是否误会了。 (2认同)

rub*_*cks 7

使用功能模板

#include <type_traits>

template< typename T >
bool is_aligned(T* p){
    return !(reinterpret_cast<uintptr_t>(p) % std::alignment_of<T>::value);
}
Run Code Online (Sandbox Code Playgroud)

你可以通过调用类似的东西在运行时检查对齐

struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes
Run Code Online (Sandbox Code Playgroud)

要检查错误的对齐失败,您可以这样做

// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
Run Code Online (Sandbox Code Playgroud)

  • 在这里解释它是如何工作的,以便OP理解它会很好. (3认同)
  • C++明确禁止创建给定类型`T`的未对齐指针.因为不允许存在这样的指针,所以允许编译器针对任何指针"p"优化"is_aligned(p)"到"true". (3认同)

atl*_*ste 5

这基本上就是我正在使用的。通过将整数用作模板,可以确保它延长了编译时间,因此无论我做什么,我都不会以缓慢的模运算结束。

我一直喜欢检查我的输入,因此喜欢编译时间断言。如果对齐值是错误的,那么它将无法编译...

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

要查看发生了什么,可以使用以下命令:

// 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;
Run Code Online (Sandbox Code Playgroud)


alf*_*lfC 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; 
Run Code Online (Sandbox Code Playgroud)

例子:

        char D[1];
        assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); //  might fail, sometimes
Run Code Online (Sandbox Code Playgroud)