在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失去作用,或者我错了吗?
编辑:请查看重复项中的答案(和/或此处的彼得的答案)。我在下面写的内容在技术上是正确的,但实际上并没有真正的意义。
是的,这将是基于C ++标准的未定义行为。您的编译器可能仍会正确地将其作为扩展进行处理(首先发现SIMD类型和内部函数不是C ++标准的一部分)。
为了安全,正确地执行此操作而不影响速度,可以使用固有函数将4个浮点数直接从内存加载到128位寄存器中:
__m128 reg = _mm_load_ps(a);
有关重要的对齐约束,请参阅《英特尔技术指南》:
__m128 _mm_load_ps (float const* mem_addr)
将内存中的128位(由4个压缩的单精度(32位)浮点元素组成)加载到中
dst
。mem_addr
必须在16字节边界上对齐,否则可能会产生一般保护异常。
英特尔的内在API确实定义了强制转换__m128*
和取消引用的行为:与_mm_load_ps
在同一指针上相同。
对于float*
和double*
,基本上存在加载/存储内在函数来包装此重新解释的类型转换,并将对齐信息传递给编译器。
如果_mm_load_ps()
支持,实现还必须定义问题中代码的行为。
我不知道这是否真的记录在任何地方;也许在英特尔的教程或白皮书,但它是商定所有的编译器的行为,我想大多数人都会同意,一个编译器没有定义这种行为并不完全支持英特尔的内部函数API。
__m128
类型定义为may_alias
1,因此就像char*
您可以将a指向__m128*
任何对象(包括int[]
or任意结构),并通过其加载或存储而不会违反strict-aliasing一样。(只要它与16对齐,否则您确实需要_mm_loadu_ps
,或者使用GNU C的aligned(1)
属性声明的自定义矢量类型)。
脚注1: __attribute__((vector_size(16), may_alias))
在GNU C中,MSVC不进行基于类型的别名分析。