是不是__m128d本地对齐?

mar*_*zzz 0 c++ sse simd memory-alignment intrinsics

我有这个代码:

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

但是,"有时",它提高访问内存冲突,我认为它是由于缺少我的3个数组的内存对齐的a,bc.

但是,由于我操作__m128d(使用__declspec(align(16))),当我转换为指针时,是否保证对齐?

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

如果是这样,你会如何在C++中为这种东西对齐数组呢?std :: align

我在Win x64,MSVC,在发布模式32和64位编译.

Pet*_*des 8

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

不要在便携式代码中使用; 我不认为MSVC支持这一点,英特尔也没有定义它.

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


顺便说一句,这是不是"特殊"或特定的内部函数:铸造void*char*int*(和取消引用它)是唯一安全的如果充分对准. 在x86-64 System V和Windows x64中alignof(int) = 4.

(有趣的事实:即使创建一个未对齐的指针也是ISO C++中未定义的行为.但是支持Intel内在函数API的编译器必须支持类似的东西_mm_loadu_si128( (__m128i*)char_ptr ),所以我们可以考虑创建而不需要取消引用未对齐的指针作为扩展的一部分.)

它通常适用于x86,因为只有16字节的加载具有对齐所需的版本.但是在SPARC上,你可能会遇到同样的问题.这可能的运行与错位指针麻烦intshort甚至在x86,虽然. 为什么在AMD64上对mmap内存的未对齐访问有时会出现段错误?这是一个很好的例子:gcc的自动矢量化假定一些整数uint16_t元素将达到16字节的对齐边界.

它也更容易遇到内在函数的问题,因为alignof(__m128d)它比大多数原始类型的对齐更大.在32位的x86 C++实现方式中,alignof(maxalign_t)只有8,因此mallocnew通常只返回8字节对准的存储器.

  • @markzzz:不,整数寄存器无论如何都是分开的,编译器可以使用指针*而不是实际的循环计数器.即它可以优化掉`i`.(您可以通过编写`const __m128d*endA =(const __m128d*)(a + bufferSize)`并使用`pA <endA`作为循环条件来帮助它.但编译器*允许*进行优化根据as-if规则,它本身.) (2认同)
  • @markzzz:*标量*整数寄存器,如`rdi`,(又名"通用"寄存器)用于保存指针和整数.(在asm中,指针只是一个整数).`__m128d*`仍然只是一个指针.所以你会有`add rdi,16` /`movapd xmm0,[rdi]`/`addpd xmm0,[rsi]`这样的指令.在https://godbolt.org/上查看你的循环的asm code-gen(启用了优化). (2认同)