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

Posted

技术标签:

【中文标题】正确使用 _mm256_maskload_ps 将少于 8 个浮点数加载到 __m256【英文标题】:Proper use of _mm256_maskload_ps for loading less than 8 floats into __m256 【发布时间】:2021-08-29 01:55:47 【问题描述】:

我无法确定需要使用_mm256_maskload_ps 设置哪些位进行屏蔽。

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

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

基本上我正在寻找一种方法来描述一个掩码,以便在我这样做时设置第一个元素中的 1,2,3...8 个_mm256_maskload_ps

注意: 有趣的是,当我的掩码为-1, 0, 0, 0 时,前两个元素被设置。当我的掩码是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 上,% 是文件名

【问题讨论】:

【参考方案1】:

双字是 32 位,而不是 64。字 = 16,双字 = 32,四字 = 64。前两个元素被选中,因为 -1 在所有 64 位中都是 1,因此当 maskload 将其视为两个将设置两个元素的最高位,而不是一个 64 位值。 0xFFFFFFFF,OTOH,是设置的最低 32 位和未设置的最高 32 位。由于 x86 是 little-endian,因此最低有效位在前,这就是为什么您最终选择了他的第一个元素而不是第二个元素。

documentation in the intrinsics guide 在这里要好得多。

请注意,在 GCC/clang 上,__m256i 是使用 vector extensions 实现的。但是,MSVC 不支持向量扩展,因此您的代码将无法在那里工作。此外,GCC 和 clang 都使用 64 位值的向量,即使 all 整数向量使用相同的 __m256i 类型,所以您可能希望使用 _mm256_set_epi32、@987654327 @ 或 _mm256_load_si256 无论如何都要创建你的 _load_mask

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

【讨论】:

“哦,以下划线开头的名称是保留的” - 错误,请再次阅读该链接。当其他规则都不适用时,“以下划线开头”仅保留在全局命名空间中,但 OP 引入的唯一符号在本地范围内。 @o11c:出于风格原因,我仍然建议不要在 var 名称中使用前导下划线,尤其是,在使用 Intel 内部函数时,很多类型和函数名称都以 _ 开头.如果你有一个数组ava 是从它加载的 SIMD 向量的一个不错的名称,如果没有更语义上有意义的短名称,你可以想出。【参考方案2】:

__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 来避免强制转换。

请参阅this answer 了解说明。

【讨论】:

以上是关于正确使用 _mm256_maskload_ps 将少于 8 个浮点数加载到 __m256的主要内容,如果未能解决你的问题,请参考以下文章

为啥 gcc 将 _mm256_permute2f128_ps 编译为 Vinsertf128 指令?

AVX指令使用

将 __m256i 存储为整数

将 __m256i 存储为整数

如何访问 256 位 ps 向量的组件

_mm256_testc_pd、_mm256_testz_pd、_mm256_testnzc_pd 是干啥用的?