我有此代码:
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));
}
}
但是“有时”它会引发访问内存冲突,我认为这是由于我的3个数组a
,b
和{{1 }}。
但是由于我对c
(使用__m128d
进行操作),当我强制转换为那些指针时,是否不能保证对齐?
还是因为它将__declspec(align(16))
用作“寄存器”,所以它可以__m128d
直接从未对齐的内存中进行寄存器注册(因此,例外)?
如果是这样,您将如何在C ++中对齐此类内容? std::align?
我使用Win x64,MSVC,并且在32位和64位发布模式下进行编译。
答案 0 :(得分: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)));
请勿在可移植代码中使用;我不认为MSVC支持此功能,而Intel也未定义它。
脚注2:根据您的情况,您还需要将2D数组的每一行与16对齐。因此,如果{{1},则数组尺寸应为[voiceSize][round_up_to_next_power_of_2(bufferSize)]
}可能很奇怪。在每行的末尾保留未使用的填充元素是一种常见的技术,例如在图形编程中处理具有潜在奇数宽度的2d图像。
顺便说一句,这不是“特殊的”或特定于内在函数:将bufferSize
或void*
投射到char*
(并取消引用)仅在其充分对齐后才是安全的。在x86-64系统V和Windows x64中,int*
。
(有趣的事实:即使创建未对齐的指针也是ISO C ++中未定义的行为。但是支持Intel内在API的编译器必须支持alignof(int) = 4
之类的东西,因此我们可以考虑在不取消引用未对齐指针的情况下进行创建扩展名。)
它通常碰巧在x86上起作用,因为只有16字节的加载具有需要对齐的版本。但是例如在SPARC上,您可能会遇到相同的问题。即使在x86上, 也可能会遇到指向_mm_loadu_si128( (__m128i*)char_ptr )
或int
的未对齐指针的麻烦。 Why does unaligned access to mmap'ed memory sometimes segfault on AMD64?是一个很好的例子:gcc的自动矢量化假定short
元素的总数将达到16字节的对齐边界。
由于uint16_t
大于大多数原始类型的对齐方式,因此更容易遇到内部函数问题。在32位x86 C ++实现中,alignof(__m128d)
只有8,因此alignof(maxalign_t)
和malloc
通常仅返回8字节对齐的内存。