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

在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的(可能是经过简历验证的)带符号或无符号的变体。
  •   
  • AliasedType是std :: byte,(从C ++ 17开始)char或unsigned char:这允许将任何对象的对象表示形式检查为字节数组。
  •   

在这种情况下,有人如何防止未定义的行为?我知道我可以进行std :: memcopy,但是性能下降会使simd失去作用,或者我错了吗?

Murphy_zhu_qiang 回答:在C ++中是否转换为simd类型的未定义行为?

修改:请查看重复项中的答案(和/或此处的彼得的答案)。我在下面写的内容在技术上是正确的,但在实践中并没有真正的意义。


是的,根据C ++标准,这将是未定义的行为。您的编译器可能仍会正确地将其作为扩展进行处理(首先发现SIMD类型和内在函数不是C ++标准的一部分)。

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

__m128 reg = _mm_load_ps(a);

有关重要的对齐约束,请参见Intel Intrinsics Guide

  

__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(),则实现还必须定义问题中代码的行为。


我不知道这是否实际记录在任何地方;也许是在Intel教程或白皮书中,但这是所有编译器都同意的行为,我想大多数人都同意没有定义此行为的编译器并不完全支持Intel的内在API

__m128类型定义为may_alias 1 ,因此像char*一样,您可以将__m128*指向任何内容,包括int[]或任意结构,并在不违反严格别名的情况下通过它加载或存储。 (只要它与16对齐,否则就需要_mm_loadu_ps或使用GNU C的aligned(1)属性声明的自定义矢量类型)。


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

本文链接:https://www.f2er.com/3082493.html

大家都在问