C++ 如果浮点数是16字节对齐的,是否可以将浮点数直接强制转换到_m128?

C++ 如果浮点数是16字节对齐的,是否可以将浮点数直接强制转换到_m128?,c++,c,alignment,sse,intrinsics,C++,C,Alignment,Sse,Intrinsics,如果浮点数是16字节对齐的,则直接将浮点数强制转换到\uu m128是否安全/可行/可取 我注意到使用\u-mm\u-load\u-ps和\u-mm\u-store\u-ps来“包装”原始数组会增加大量开销 我应该注意哪些潜在的陷阱 编辑: 实际上,在使用load和store指令时没有开销,我混合了一些数字,这就是为什么我获得了更好的性能。即使在\uum128实例中,我也能对原始内存地址进行一些可怕的破坏,当我运行测试时,在没有\umm\u load\ups指令的情况下,完成测试所用的时间是原来

如果浮点数是16字节对齐的,则直接将浮点数强制转换到
\uu m128
是否安全/可行/可取

我注意到使用
\u-mm\u-load\u-ps
\u-mm\u-store\u-ps
来“包装”原始数组会增加大量开销

我应该注意哪些潜在的陷阱

编辑:

实际上,在使用load和store指令时没有开销,我混合了一些数字,这就是为什么我获得了更好的性能。即使在
\uum128
实例中,我也能对原始内存地址进行一些可怕的破坏,当我运行测试时,在没有
\umm\u load\ups
指令的情况下,完成测试所用的时间是原来的两倍,可能会返回到某种故障安全代码路径。

经过验证,这是可能的,但不安全或不推荐

您不应直接访问\u m128字段


原因如下:

  • 将浮子*铸造到m128将不起作用。C++编译器将分配到α128型转换为SSE指令,加载4个浮点数到SSE登记器。假设编译了此转换,它不会创建工作代码,因为不会生成SEE loading指令
  • __m128变量实际上不是变量或数组。这是SSE寄存器的占位符,用C++编译器代替SSE汇编指令登记。要更好地理解这一点,请阅读《英特尔汇编编程参考》


    我能看到的一个明显的问题是,你没有使用别名(通过一个以上的指针类型引用一个内存位置),这会让优化者感到困惑。别名的典型问题是,由于优化程序没有观察到您正在通过原始指针修改内存位置,因此它认为它没有改变

    由于您显然没有充分使用Optimizer(或者您愿意依靠它来发出正确的SSE指令),因此您可能不会有问题


    自己使用内部函数的问题是,它们被设计为在SSE寄存器上操作,不能使用从内存位置加载的指令变体,并在单个指令中处理它。

    是什么让你认为
    \u mm\u load\u ps
    \u mm\u store\u ps
    会“增加大量开销”?假设源/目标是内存,这是向SSE寄存器加载/存储浮点数据的正常方法(任何其他方法最终都归结为内存)。

    有几种方法将
    浮点值放入SSE寄存器;可以使用以下内部函数:

    __m128 sseval;
    float a, b, c, d;
    
    sseval = _mm_set_ps(a, b, c, d);  // make vector from [ a, b, c, d ]
    sseval = _mm_setr_ps(a, b, c, d); // make vector from [ d, c, b, a ]
    sseval = _mm_load_ps(&a);         // ill-specified here - "a" not float[] ...
                                      // same as _mm_set_ps(a[0], a[1], a[2], a[3])
                                      // if you have an actual array
    
    sseval = _mm_set1_ps(a);          // make vector from [ a, a, a, a ]
    sseval = _mm_load1_ps(&a);        // load from &a, replicate - same as previous
    
    sseval = _mm_set_ss(a);           // make vector from [ a, 0, 0, 0 ]
    sseval = _mm_load_ss(&a);         // load from &a, zero others - same as prev
    
    无论您是声明
    \u mm\u set\u ss(val)
    还是
    \u mm\u load\u ss(&val)
    ,编译器通常都会创建相同的指令-请尝试并反汇编代码


    在某些情况下,编写
    \u mm\u set\u ss(*valptr)
    而不是
    \u mm\u load\u ss(valptr)
    是有利的。。。取决于你的代码(结构)。

    自从这个问题被提出以来,几年过去了。要回答这个问题,我的经验表明:

    reinterpret\u cast
    -将
    float*
    转换为
    \uuu m128*
    ,反之亦然,只要
    float*
    是16字节对齐的-例如(在MSVC 2012中):

    uu declspec(align(16))浮点f[4];
    返回_mm_mul_ps(_mm_set_ps1(1.f),*重新解释(f));
    
    是的,我看到了这一点,但没有解释为什么我觉得它没有什么价值。这更像是我想知道这样做的陷阱,因为我计划:)嗯,好吧,仔细看,似乎
    \uuuum128
    是用
    \uuuuu属性((向量大小(16))定义的)
    (请参阅)。我想直接强制转换到
    \uuum128
    可能实际上没有正确地使用指定的寄存器进行此类操作?很抱歉,事情似乎发生了变化:\uuuum128现在实际上被声明为具有相应成员数组的联合。只要满足
    浮动*
    上的对齐要求,将
    浮动*
    强制转换为
    \uu m128*
    也可以。(编辑:我在Windows上,使用VS2012)@St0fF有趣。也许你应该把它变成一个答案?这个答案的第二部分是假的,除非MSVC是完全奇怪的。取消引用
    \uuu m128*
    很好,并生成对齐的加载/存储。如果这不是你想要的,那就不要做。因为我确实分析过它。在标量中添加相同长度的数组需要0.337秒,在具有加载和存储功能的SSE中需要0.244秒,而在没有任何转换(使用_m128的数组)的情况下,相同的操作需要0.127秒——几乎是速度的两倍!实际上,数字各不相同,但是一个_m128数组总是比使用加载和存储函数以及一个原始浮点数组快得多。50%的时候它的速度是原来的两倍多,有时没有那么快。我想你可能误解了你的分析结果。这听起来像是将显式加载/存储与编译器生成的加载/存储进行比较,但同样的指令很可能是在“幕后”使用的-您只是看到了不同指令调度/循环展开/等的效果。尽管查看您的代码以了解您正在测量的是什么,这将是非常有用的。Paul-您似乎是对的,较低的时间实际上是由于我的疏忽造成的一些数字不匹配。如果没有加载和存储函数,操作实际上需要更长的时间,但仍然是准确完成的,可能会退回到某种故障安全状态。10x,我可能会选择类似的实现我认为内部函数种类繁多的最大原因是a)程序员可以选择直接使用常量而不是变量(例如,
    \u m128 s=\u mm\u set1\u ps(M\u PI)
    而不是
    浮点PI[4]={M\u PI,M\u PI,M\u PI};\u m128 s=\u mm\u load\u ps(PI)
    ),以及b)允许编译器优化某些
    __declspec( align( 16 ) ) float f[4];
    return _mm_mul_ps( _mm_set_ps1( 1.f ), *reinterpret_cast<__m128*>( f ) );