__m128
如果它们是 16 字节对齐的,直接将浮点数转换为安全/可能/可取吗?
我注意到使用_mm_load_ps
和_mm_store_ps
“包装”原始数组会增加大量开销。
我应该注意哪些潜在的陷阱?
编辑 :
使用加载和存储指令实际上没有开销,我混合了一些数字,这就是我获得更好性能的原因。即使你我能够在一个实例中对原始内存地址进行一些可怕的修改__m128
,当我运行测试时,在没有_mm_load_ps
指令的情况下完成了两倍的时间,可能会退回到一些故障安全代码路径。
__m128
如果它们是 16 字节对齐的,直接将浮点数转换为安全/可能/可取吗?
我注意到使用_mm_load_ps
和_mm_store_ps
“包装”原始数组会增加大量开销。
我应该注意哪些潜在的陷阱?
编辑 :
使用加载和存储指令实际上没有开销,我混合了一些数字,这就是我获得更好性能的原因。即使你我能够在一个实例中对原始内存地址进行一些可怕的修改__m128
,当我运行测试时,在没有_mm_load_ps
指令的情况下完成了两倍的时间,可能会退回到一些故障安全代码路径。
是什么让您这么认为_mm_load_ps
并_mm_store_ps
“增加了巨大的开销”?这是向/从 SSE 寄存器加载/存储浮点数据的正常方法,假设源/目标是内存(并且任何其他方法最终都归结为这一点)。
有几种方法可以将float
值放入 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
无论您是否声明_mm_set_ss(val)
或_mm_load_ss(&val)
尝试它并反汇编您的代码,编译器通常都会创建相同的指令。
在某些情况下,编写_mm_set_ss(*valptr)
而不是_mm_load_ss(valptr)
......取决于您的代码(的结构),这可能是有利的。
通过http://msdn.microsoft.com/en-us/library/ayeb3ayc.aspx,这是可能的,但不安全或不推荐。
您不应直接访问__m128字段。
原因如下:
- 将 float* 转换为 __m128 将不起作用。C++ 编译器将赋值为 __m128 类型转换为 SSE 指令,将 4 个浮点数加载到 SSE 寄存器。假设这个转换被编译,它不会创建工作代码,因为没有生成 SEE 加载指令。
__m128 变量实际上不是变量或数组。这是 SSE 寄存器的占位符,由 C++ 编译器替换为 SSE 汇编指令。要更好地理解这一点,请阅读英特尔汇编编程参考。
自从提出这个问题以来已经过去了几年。要回答我的经验表明的问题:
是的
reinterpret_cast
- 将 afloat*
转换为 a __m128*
,反之亦然,只要它float*
是 16 字节对齐的 - 示例(在 MSVC 2012 中):
__declspec( align( 16 ) ) float f[4];
return _mm_mul_ps( _mm_set_ps1( 1.f ), *reinterpret_cast<__m128*>( f ) );
我可以看到的明显问题是,您不是别名(通过多个指针类型引用内存位置),这可能会使优化器感到困惑。别名的典型问题是,由于优化器没有观察到您正在通过原始指针修改内存位置,因此它认为它没有改变。
由于您显然没有充分使用优化器(或者您愿意依靠它来发出正确的 SSE 指令),您可能会没事的。
自己使用内在函数的问题在于它们被设计为在 SSE 寄存器上运行,并且不能使用从内存位置加载并在单个指令中处理它的指令变体。