__m128d 不是原生对齐的吗?

Isn't __m128d aligned natively?

我有这个代码:

double a[bufferSize];
double b[voiceSize][bufferSize];
double c[voiceSize][bufferSize];

...

inline void AddIntrinsics(int voiceIndex, int blockSize) {
    // assuming blockSize / 2 == 0 and voiceIndex is within the range
    int iters = blockSize / 2;
    __m128d *pA = (__m128d*)a;
    __m128d *pB = (__m128d*)b[voiceIndex];
    double *pC = c[voiceIndex];

    for (int i = 0; i < iters; i++, pA++, pB++, pC += 2) {
        _mm_store_pd(pC, _mm_add_pd(*pA, *pB));
    }   
}

但是 "sometimes" 它引发了 访问内存冲突 ,我认为这是由于我的 3 个数组 abc

但是因为我在 __m128d 上操作(它使用 __declspec(align(16))),当我投射到那些指针时不保证对齐吗?

或者因为它将 __m128d 用作 "register",它可以 mov 直接从未对齐的内存注册(因此,例外)?

如果是这样,您将如何在 C++ 中为此类内容对齐数组? std::align?

我在 Win x64、MSVC 上,在 32 位和 64 位发布模式下编译。

__m128d 是一种假定/要求/保证(对编译器)16 字节对齐的类型1.

将未对齐的指针转换为 __m128d* 并取消引用它是未定义的行为,这是预期的结果。 如果您的数据可能未对齐,请使用 _mm_loadu_pd(或者最好将您的数据与 alignas(16) double a[bufferSize]; 2 对齐)。 ISO C++11 及更高版本具有用于对齐静态和自动存储的可移植语法(但动态存储不那么容易)。

将指针转换为 __m128d* 并取消引用它就像向编译器承诺它 对齐的。 C++ 让您欺骗编译器,可能会导致灾难性的后果。 执行需要对齐的操作不会追溯对齐您的数据;当您单独编译多个文件或通过指针操作时,这没有意义甚至不可能。


脚注 1:有趣的事实:GCC 对 Intel 内在函数的实现 API 添加了一个 __m128d_u 类型:未对齐的向量,如果您取消引用指针,则意味着 1 字节对齐。

typedef double __m128d_u 
       __attribute__ ((__vector_size__ (16), __may_alias__, __aligned__ (1)));

不要在可移植代码中使用;我不认为 MSVC 支持这个,Intel 也没有定义它。

脚注 2:在您的情况下,您还需要 二维数组的每一行 以 16 对齐。因此您需要数组维度为 [voiceSize][round_up_to_next_power_of_2(bufferSize)]如果 bufferSize 可以是奇数。在每一行的末尾留下未使用的填充元素是一种常见的技术,例如在具有潜在奇数宽度的二维图像的图形编程中。


顺便说一句,这不是 "special" 或特定于内在函数:将 void*char* 转换为 int* (并取消引用它)是只有充分对齐才安全。 在 x86-64 System V 和 Windows x64 中,alignof(int) = 4.

(有趣的事实:即使创建未对齐的指针在 ISO C++ 中也是未定义的行为。但是支持英特尔内在函数 API 的编译器必须支持 _mm_loadu_si128( (__m128i*)char_ptr ) 之类的东西,因此我们可以考虑在不取消引用的情况下创建未对齐的指针作为扩展的一部分。)

它通常恰好在 x86 上工作,因为只有 16 字节加载具有需要对齐的版本。但例如在 SPARC 上,您可能会遇到同样的问题。不过,即使在 x86 上, 也可能 运行 因指向 intshort 的未对齐指针而陷入麻烦。 是一个很好的例子:gcc 的自动矢量化假设一些整数的 uint16_t 元素将达到 16 字节对齐边界。

也更容易 运行 解决内在问题,因为 alignof(__m128d) 比大多数原始类型的对齐方式都要大。在 32 位 x86 C++ 实现上,alignof(maxalign_t) 只有 8,因此 mallocnew 通常只有 return 8 字节对齐内存。