在C++中,转换为simd类型是否有未定义的行为

Is casting to simd-type undefined behaviour in C++?

本文关键字:是否 未定义 类型 simd C++ 转换      更新时间:2023-10-16

在simd教程中,我发现了以下代码片段。

void simd(float* a, int N)                                                                                                                                                                                        
{                      
// We assume N % 4 == 0.                                                                                                                                                                                        
int nb_iters = N / 4;                                                                                                                                                                                         
__m128* ptr = reinterpret_cast<__m128*>(a); // (*)                                                                                                                                                                                 
for (int i = 0; i < nb_iters; ++i, ++ptr, a += 4)                                                                                                                                                              
_mm_store_ps(a, _mm_sqrt_ps(*ptr));                                                                                                                                                                          
}   

现在我的问题是,这条线是(*(未定义的行为吗?由于以下规范(https://en.cppreference.com/w/cpp/language/reinterpret_cast)

每当试图通过AliasedType类型的glvalue读取或修改DynamicType类型对象的存储值时,除非以下情况之一为真,否则行为是未定义的:

  • AliasedType和DynamicType相似
  • AliasedType是DynamicType的有符号或无符号变体(可能是cv限定的(
  • AliasedType是std::byte、(由于C++17(char或unsigned char:这允许将任何对象的对象表示检查为字节数组

在这种情况下,如何防止未定义的行为?我知道我可以std::memcopy,但性能惩罚会使simd变得无用,或者我错了吗?

编辑:请查看副本中的答案(和/或Peter的答案(。我在下面写的内容在技术上是正确的,但在实践中并不真正相关


是的,这将是基于C++标准的未定义行为。编译器可能仍然可以将其作为扩展正确处理(因为SIMD类型和内部函数不是C++标准的一部分(。

为了在不影响速度的情况下安全正确地执行此操作,您可以使用内在函数将4个浮点直接从内存加载到128位寄存器中:

__m128 reg = _mm_load_ps(a);

有关重要的对齐约束:,请参阅"英特尔Intrnsics指南">

__m128 _mm_load_ps (float const* mem_addr)

将128位(由4个压缩的单精度(32位(浮点元素组成(从内存加载到dst中。mem_addr必须在16字节边界上对齐,否则可能会生成一般保护异常。

Intel的intrinsic API定义了强制转换为__m128*和取消引用的行为:它与同一指针上的_mm_load_ps相同。

对于float*double*,基本上存在加载/存储内部函数来包装此重新解释强制转换并将对齐信息传递给编译器。

如果支持_mm_load_ps(),则实现还必须定义问题中代码的行为


我不知道这是否真的记录在任何地方;也许是在英特尔教程或白皮书中,但这是所有编译器公认的行为,我想大多数人都会同意没有定义这种行为的编译器并不完全支持英特尔的内部API。

__m128类型被定义为may_alias1,因此与char*一样,您可以将__m128*指向任何东西,包括int[]或任意结构,并且通过它加载或存储而不违反严格的别名。(只要它被16对齐,否则你确实需要_mm_loadu_ps,或者用类似GNUC的aligned(1)属性声明的自定义向量类型(。


脚注1:GNU C中的__attribute__((vector_size(16), may_alias)),并且MSVC不进行基于类型的别名分析。