当我需要进行向量操作时,操作数只是一个广播到每个组件的浮点数,我应该预先计算__m256
或__m128
,并在需要时加载它,或者每次我需要向量时,使用_mm_set1_ps
将浮动广播到寄存器?
我一直在预先计算非常重要和高度使用的向量,并在运行中生成那些不太重要的向量。但我真的通过预先计算获得了任何速度吗?这值得吗?
_mm_set1_ps
是用一条指令实现的吗?这可能会回答我的问题。
答案 0 :(得分:3)
我认为通常最好从您的代码中分解您的SSE向量(例如循环),并在需要时使用它,假设您注意不要意外强制它进入记忆。 (例如,如果您获取其地址或通过引用传递给另一个函数,那么它可能会被强制进入内存,您可能会遇到奇怪的行为。)
这个想法是通常最好避免将值传入和传出SSE寄存器,如果在特定情况下发生这种情况,则编译器已经知道如何价值是构建的,如果需要可以rematerialize。我认为这比一般的loop-invariant code motion要容易得多,这是反向优化(即编译器将其排除在外的位置),这需要编译器证明代码确实是循环不变的。
答案 1 :(得分:3)
我正在播放广播以获得fastest way to fill a vector (SSE2) with a certain value. Templates friendly的答案。看看一些asm转储的广播。
set1
每次使用它都不会产生太大的影响,只要编译器知道要广播的值并不是别的。 (如果编译器不能假设它没有别名,则必须在每次写入可能别名的数组或指针后重做广播。)
将set1
结果存储在命名变量中通常很好。如果编译器用完了向量寄存器,它可能会将向量溢出到堆栈,然后重新加载,或者它可能会重新广播。我不确定编码风格是否会影响这一决定。
我不会在调用函数之间使用static const
变量来缓存它。 (这可能导致编译器生成代码以检查变量是否已在每次调用时初始化。)
编译时常量的广播有时会导致编译时广播,所以你的代码只有16B的const数据存在于内存中。
AVX1广播已经存在于寄存器中的值是最坏的情况。 AVX1仅提供内存源vbroadcastps
(仅使用加载端口)。广播需要shufps / vinsertf128
。
vbroadcastps ymm, xmm
需要AVX2(使用随机播放端口)。
答案 2 :(得分:2)
当然它很大程度上取决于你的代码,但我已经使用这两种方法实现了两个简单的函数。 See code
__m128 calc_set1(float num1, float num2)
{
__m128 num1_4 = _mm_set1_ps(num1);
__m128 num2_4 = _mm_set1_ps(num2);
__m128 result4 = _mm_mul_ps(num1_4, num2_4);
return result4;
}
__m128 calc_mov(float* num1_4_addr, float* num2_4_addr)
{
__m128 num1_4 = _mm_load_ps(num1_4_addr);
__m128 num2_4 = _mm_load_ps(num2_4_addr);
__m128 result4 = _mm_mul_ps(num1_4, num2_4);
return result4;
}
和汇编
calc_set1(float, float):
shufps $0, %xmm0, %xmm0
shufps $0, %xmm1, %xmm1
mulps %xmm1, %xmm0
ret
calc_mov(float*, float*):
movaps (%rdi), %xmm0
mulps (%rsi), %xmm0
ret
您可以看到calc_mov()
按照您的预期执行,calc_set1()
使用单个随机播放指令。
如果L1缓存的加载端口忙碌,则movps
指令可能需要大约四个周期才能生成地址+更多,在极少数情况下缓存未命中时会更多。
shufps
将在最近的任何英特尔微体系结构上进行一个周期。无论是SSE128还是AVX256,我都相信这是真的。因此,我建议使用mm_set1_ps
方法。
当然,shuffle指令假定float已经在SSE / AVX寄存器中。如果您从内存中加载它,那么广播会更好,因为它会在单个指令中捕获movps
和shufps
中的最佳内容。