AVX2 float 比较并得到 0.0 或 1.0 而不是全 0 或全 1 位

AVX2 float compare and get 0.0 or 1.0 instead of all-0 or all-one bits

基本上,在生成的向量中,我想为所有 > 1 的输入浮点值保存 1.0,而为所有 <= 1 的输入浮点值保存 0.0。这是我的代码,

float f[8] = {1.2, 0.5, 1.7, 1.9, 0.34, 22.9, 18.6, 0.7};
float r[8]; // Must be {1, 0, 1, 1, 0, 1, 1, 0}

__m256i tmp1 = _mm256_cvttps_epi32(_mm256_loadu_ps(f));
__m256i tmp2 = _mm256_cmpgt_epi32(tmp1, _mm256_set1_epi32(1));
_mm256_store_ps(r, _mm256_cvtepi32_ps(tmp2));

for(int i = 0; i < 8; i++)
    std::cout << f[i] << " : " << r[i] << std::endl;

但我没有得到正确的结果。这就是我得到的。为什么 AVX2 关系操作对我来说不能正常工作?

1.2 : 0
0.5 : 0
1.7 : 0
1.9 : 0
0.34 : 0
22.9 : -1
18.6 : -1
0.7 : 0

当使用 float 转换为 int 时 _mm256_cvttps_epi32 那么返回的整数是截断的(向零舍入)值。即1.2、1.7、1.9转换为1,不大于1。

_mm256_cmpgt_epi32 的输出不是 1,而是 "all ones",来自 docs

... if the s1 data element is greater than the corresponding element in s2, then the corresponding element in the destination vector is set to all 1s.

"All ones" 是在使用二进制补码整数时,如您的结果所示,减一。

题外话:

  • 为什么要使用未对齐的加载和对齐的存储?
  • 你应该看看_mm256_cmp_ps

我认为您的问题最好使用 _mm256_cmp_ps。为此,我实施了以下程序。这比你想要的更多。如果你想保存一个,你应该将所有 mask 元素设置为 1,但如果你想保存另一个数字,你可以将掩码值更改为任何你想要的。

//gcc 6.2, Linux-mint, Skylake 
#include <stdio.h>
#include <x86intrin.h>

float __attribute__(( aligned(32))) f[8] = {1.2, 0.5, 1.7, 1.9, 0.34, 22.9, 18.6, 1.0};
// float __attribute__(( aligned(32))) r[8]; // Must be {1, 0, 1, 1, 0, 1, 1, 0}
// in C++11, use alignas(32).  Or C11 _Alignas(32), instead of GNU C __attribute__.

void printVecps(__m256 vec)
{
    float tempps[8];
    _mm256_store_ps(&tempps[0], vec);
    printf(" [0]=%3.2f, [1]=%3.2f, [2]=%3.2f, [3]=%3.2f, [4]=%3.2f, [5]=%3.2f, [6]=%3.2f, [7]=%3.2f \n",
    tempps[0],tempps[1],tempps[2],tempps[3],tempps[4],tempps[5],tempps[6],tempps[7]) ;

}

int main()
{

    __m256 mask = _mm256_set1_ps(1.0), vec1, vec2, vec3;

    vec1 = _mm256_load_ps(&f[0]);                   printf("vec1 : ");printVecps(vec1); // load vector values from f[0]-f[7]
    vec2 = _mm256_cmp_ps ( mask, vec1, _CMP_LT_OS /*0x1*/);
                                                    printf("vec2 : ");printVecps(vec2); // compare them to mask (less)
    vec3 = _mm256_min_ps (vec2 , mask);             printf("vec3 : ");printVecps(vec3); // select minimum from mask and compared results

    return 0;
}

mask = {1,1,1,1,1,1,1,1} 的输出是:

vec1 :  [0]=1.20, [1]=0.50, [2]=1.70, [3]=1.90, [4]=0.34, [5]=22.90, [6]=18.60, [7]=1.00 
vec2 :  [0]=-nan, [1]=0.00, [2]=-nan, [3]=-nan, [4]=0.00, [5]=-nan, [6]=-nan, [7]=0.00 
vec3 :  [0]=1.00, [1]=0.00, [2]=1.00, [3]=1.00, [4]=0.00, [5]=1.00, [6]=1.00, [7]=0.00 

mask = {2,2,2,2,2,2,2,2} 是:

vec1 :  [0]=1.20, [1]=0.50, [2]=1.70, [3]=1.90, [4]=0.34, [5]=22.90, [6]=18.60, [7]=1.00 
vec2 :  [0]=0.00, [1]=0.00, [2]=0.00, [3]=0.00, [4]=0.00, [5]=-nan, [6]=-nan, [7]=0.00 
vec3 :  [0]=0.00, [1]=0.00, [2]=0.00, [3]=0.00, [4]=0.00, [5]=2.00, [6]=2.00, [7]=0.00 

这取决于 _mm256_min_ps 与 NaN 的非交换行为,以用 1.0 替换 NaN 元素。 NaN > 1.0 : NaN : 1.0 = 1.0,因为 NaN > anything 总是假的。

注意 (即使它知道 minps 指令不是)。使用最新的 gcc,或确保 gcc 选择按照此算法所需的顺序使用操作数编译您的代码。 (或使用 clang)。 gcc 可能永远不会与 AVX 交换操作数,只会与 SSE 交换(以避免额外的 movapd 指令),但最安全的是使用 gcc7 或更高版本。