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

For for an SSE vector that has all the same components, generate on the fly or precompute?

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

我一直在预先计算非常重要且使用频繁的向量,并即时生成不太重要的向量。但是我真的通过预计算获得了任何速度吗?值得这么麻烦吗?

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

我认为通常最好从代码(例如循环)中分离出 SSE 向量,并在需要时使用它,假设你注意不要意外地强制它进入记忆。 (例如,如果你获取它的地址或通过引用将它传递给另一个函数,那么它可能会被强制进入内存并且你可能会得到奇怪的行为。)
这个想法是 通常 最好避免将值传入和传出 SSE 寄存器,如果碰巧在您的特定情况下不是这种情况,编译器已经知道如何该值是构造出来的,通常可以rematerialize it if need be. I think this is a lot easier than loop-invariant code motion,这是反向优化(即编译器为您分解出来的地方)并且需要编译器证明代码确实是循环不变的。

当然这在很大程度上取决于您的代码,但我已经使用这两种方法实现了两个简单的功能。 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  [=11=], %xmm0, %xmm0
    shufps  [=11=], %xmm1, %xmm1
    mulps   %xmm1, %xmm0
    ret
calc_mov(float*, float*):
    movaps  (%rdi), %xmm0
    mulps   (%rsi), %xmm0
    ret

您可以看到 calc_mov() 的表现符合您的预期,而 calc_set1() 使用单个随机播放指令。

一个movps指令可能需要大约四个周期来生成地址+如果L1缓存的加载端口繁忙+更多在缓存未命中的罕见事件中。

shufps 将在任何最新的英特尔微体系结构上占用一个周期。我相信无论是 SSE128 还是 AVX256 都是如此。因此我建议使用 mm_set1_ps 方法。

当然,洗牌指令假定浮点数已经在 SSE/AVX 寄存器中。如果您从内存中加载它,那么广播会更好,因为它会在一条指令中捕获 movpsshufps 的精华。

我正在玩广播来寻找 fastest way to fill a vector (SSE2) with a certain value. Templates friendly 的答案。看看一些广播的 asm 转储。

set1 每次使用应该不会有太大区别,只要编译器知道要广播的值不会别名任何东西。 (如果编译器不能假设它没有别名,它将不得不在每次写入可能有别名的数组或指针后重做广播。)

set1 结果存储在命名变量中通常是一种很好的风格。如果编译器用完向量寄存器,它可能会将向量溢出到堆栈,稍后重新加载,或者它可能会重新广播。我不确定编码风格是否会影响这个决定。

我不会使用 static const 变量在函数调用之间缓存它。 (这可能导致编译器生成代码来检查变量是否已在每次调用时初始化。)

编译时常量的广播有时会导致编译时广播,因此您的代码在内存中只有 16B 的常量数据。

AVX1 广播寄存器中已有的值是最坏的情况。 AVX1 仅提供内存源 vbroadcastps(仅使用加载端口)。一次广播需要 shufps / vinsertf128.

vbroadcastps ymm, xmm(使用 shuffle 端口)需要 AVX2。