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

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

本文关键字:动态 计算 向量 于具 组件 SSE      更新时间:2023-10-16

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

我一直在预先计算非常重要和高度使用的向量,并在飞行中生成不太重要的向量。但是预计算真的能提高我的速度吗?值得这么麻烦吗?

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

我认为通常最好将SSE向量从代码中提取出来(例如循环),并在需要时使用它,假设您注意不要意外地将其强制放入内存。(例如,如果你取它的地址或通过引用传递给另一个函数,那么它可能会被强制进入内存,你可能会得到奇怪的行为。)
这个想法是,通常最好避免在SSE寄存器中输入和输出值,如果在您的特定情况下不是这种情况,编译器已经知道值是如何构造的,并且可以在需要时重新实现它。我认为这比一般的循环不变代码运动要容易得多,这是反向优化(即编译器为您考虑的地方),并且需要编译器证明代码确实是循环不变的。

我正在播放广播,以最快的方式填充向量(SSE2)具有一定的值。模板友好。看一看广播的asm转储。

每次使用

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

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

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

编译时常量的广播有时会导致编译时广播,所以你的代码在内存中只有16B的const数据。

AVX1广播寄存器中已经存在的值是最坏的情况。AVX1只提供内存源vbroadcastps(只使用加载端口)。广播使用shufps / vinsertf128

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

当然,这很大程度上取决于您的代码,但我已经使用这两种方法实现了两个简单的函数。看到代码

__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()使用了一个shuffle指令。

movps指令大约需要4个周期来生成地址,如果L1缓存的加载端口很忙,则需要更多周期,如果发生缓存丢失,则需要更多周期。

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

当然,shuffle指令假设浮点数已经在SSE/AVX寄存器中。如果您从内存加载它,那么广播将会更好,因为它将在单个指令中捕获movpsshufps的最佳性能。