正确使用 _mm256_maskload_ps 将少于 8 个浮点数加载到 __m256

Proper use of _mm256_maskload_ps for loading less than 8 floats into __m256

我无法确定需要使用 _mm256_maskload_ps.

为屏蔽设置哪些位

documentation 声明掩码是“根据掩码寄存器的每个双字的最高有效位计算的整数值”

解析出来,我认为有 4 个 64 位整数。我想屏蔽 8 个值,所以我可以将其视为 8 个 32 位整数(这是我的理解变得不稳定的地方),每个整数都有一个为符号保留的 MSB,1 为负,0 为正。所以我可以为 8 个 32 位整数设置 -1 表示“请加载这个”,0 表示“不要加载这个”,我的掩码应该是正确的。但是,我们实际上有 4 个 64 位整数,所以也许我必须打包它们?

本质上,我正在寻找一种方法来描述掩码,以便在我执行 _mm256_maskload_ps

时设置第一个元素的 1,2,3...8

注意: 有趣的是,当我的掩码是 {-1, 0, 0, 0} 时,前 2 个元素被设置。当我的掩码是 {0xFFFFFFFF, 0, 0, 0} 时,只有第一个元素被设置。

#include <iostream>
#include <immintrin.h>
#include <string>

using namespace std;

int main()
{
  float a[3] {1,2,3};
  float b[3] {11, 22, 33};

  auto disp = [](float *arr) {
    cout << "[";
    string sep;
    for (size_t i = 0; i < 3; i++)
    {
      cout << sep << arr[i];
      sep = ", ";
    }
    cout << "]";
    cout << endl;
  };
  disp(a);
  disp(b);

  __m256 _a, _b;
  __m256i _load_mask = {-1, 0, 0, 0};


  _a = _mm256_maskload_ps(a, _load_mask);
  _b = _mm256_maskload_ps(b, _load_mask);
  _a = _mm256_add_ps(_a, _b);


  float c[8];
  _mm256_storeu_ps(c, _a);
  disp(c);

  return 0;
}

显示器

[1, 2, 3]
[11, 22, 33]
[12, 24, 0]

编译时

!clang++ -mavx -Wall -Wextra -std=c++17 -stdlib=libc++ -ggdb % -o $(basename -s .cpp %

在我的 mac 上,其中 % 是文件名

一个双字是 32 位,而不是 64 位。字 =​​ 16,双字 = 32,四字 = 64。选择前两个元素是因为 -1 是所有 64 位的全一,所以当掩码加载处理它时作为两个 32 位值而不是一个 64 位值,两个元素的最高位将被设置。 0xFFFFFFFF,OTOH,是设置的最低 32 位和未设置的最高 32 位。由于 x86 是小尾数法,最低有效位在前,这就是为什么您最终选择了第一个元素而不是第二个元素。

这里的documentation in the intrinsics guide好多了。

请注意,在 GCC/clang 上,__m256i 是使用 vector extensions 实现的。但是,MSVC 不支持矢量扩展,因此您的代码无法在其中运行。此外,即使相同的 __m256i 类型用于 all 整数向量,GCC 和 clang 都使用 64 位值的向量,因此您可能想要使用 _mm256_set_epi32_mm256_setr_epi32_mm256_load_si256 来创建您的 _load_mask

哦,C 和 C++ 中的名称都以下划线 are reserved 开头。不要那样做。如果你真的需要传达它是一个内部变量或其他东西,你可以使用尾随下划线,但我真的没有看到在你上面发布的代码中这样做的理由。

您的 __m256i 类型中存储的整数是 64 位整数。当您使用 -1 时,会将所有 64 位设置为 1(即 _load_mask 中的前两个 32 位整数)。使用 0xFFFFFFFF 只会设置 32 位,导致第一个整数设置了 MSB,而第二个(和其他六个)则不会。

您不应该以这种方式初始化 YMM 寄存器之一。 (这是不可移植的,因为其他编译器对 __m256i 和其他 SSE/AVX 类型使用联合,并且聚合初始化将初始化联合的第一个成员,可能是 8 字节整数。)

您应该为其使用适当的内部函数,在这种情况下:

static const int32_t mask_bits[8] = { -1, -1, 0, 0, 0, 0, 0, 0};
_mm256_loadu_si256((const __m256i*)mask_bits);

如果你有 AVX512 支持,你可以使用 _mm256_loadu_epi32 来避免转换。

有关解释,请参阅