对于具有所有相同组件的SSE向量,可以动态生成还是预先计算?

时间:2015-08-05 21:42:15

标签: c++ sse simd avx

当我需要进行向量操作时,操作数只是一个广播到每个组件的浮点数,我应该预先计算__m256__m128,并在需要时加载它,或者每次我需要向量时,使用_mm_set1_ps将浮动广播到寄存器?

我一直在预先计算非常重要和高度使用的向量,并在运行中生成那些不太重要的向量。但我真的通过预先计算获得了任何速度吗?这值得吗?

_mm_set1_ps是用一条指令实现的吗?这可能会回答我的问题。

3 个答案:

答案 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寄存器中。如果您从内存中加载它,那么广播会更好,因为它会在单个指令中捕获movpsshufps中的最佳内容。