剩余部分计算采用模数"%"和按位和"&"在AVX2上导致不同的结果

时间:2017-04-10 12:04:26

标签: c operators intel logical-operators avx2

我试图通过使用Intel intrinsincs for AVX2将浮点值转换为整数值。我的简单代码如下:

void convert_f2i(float *fin, int *iout, int iLen)
{
    int i, index, iDiv8, iLeft;
    int *iin1;  
    __m256 v0;
    __m256i vi0;
    iDiv8 = iLen/8;

    for(i=0; i<iDiv8; i++) 
    {
        v0 = _mm256_load_ps(fin+i*8);   
        vi0 = _mm256_cvttps_epi32(v0);          
        _mm256_store_si256((__m256i *)(iout+i*8),  vi0);
    }
    iLeft = iLen%8;
/*  iLeft = iLen&7;*/
    if (iLeft)
    {        
        v0  = _mm256_load_ps(fin+i*8);
        vi0 = _mm256_cvttps_epi32(v0);  
        iin1 = (int *)&vi0;
        for(i=0; i<iLeft; i++)             
        {    
            index = iLen-iLeft+i;  
            printf("iLeft:%d i:%d %d  %d index:%d\n", iLeft, i, iin1[i], ((int *)&vi0)[i], index);
            iout[index] = iin1[i];
        }                                   
    }
}

我正在运行iLen = 28671的代码。前28664个结果是正确的。但最后7个结果是有问题的。 如果我使用&#34; iLeft = iLen%8&#34;编译代码行打开,我得到以下结果: /*compiled with iLeft = iLen%8 */ iLeft:7 i:0 9 9 index:28664 iLeft:7 i:1 4 4 index:28665 iLeft:7 i:2 9 9 index:28666 iLeft:7 i:3 6 6 index:28667 iLeft:7 i:4 4 4 index:28668 iLeft:7 i:5 2 2 index:28669 iLeft:7 i:6 1 1 index:28670这是正确的。

另一方面,如果我使用&#34; iLeft = iLen&amp; 7&#34;编译代码。行打开,我得到以下结果:/*compiled with iLeft = iLen&7 */ iLeft:7 i:0 3 3 index:28664 iLeft:7 i:1 6 6 index:28665 iLeft:7 i:2 3 3 index:28666 iLeft:7 i:3 8 8 index:28667 iLeft:7 i:4 0 0 index:28668 iLeft:7 i:5 3 3 index:28669 iLeft:7 i:6 5 5 index:28670这是不正确的。

预期结果为9-4-9-6-4-2-1,而3-6-3-8-0-3-5是28656-28662之间的指数结果。当我以不同的方式计算iLeft时,我不明白发生了什么变化。在两种方式中,iLeft = 7,但结果并不相同。

有人可以告诉我可能是什么问题吗?

1 个答案:

答案 0 :(得分:1)

因为iLeft在两种情况下具有相同的值,所以OP看到的差异的真正原因必须是其他地方。

我个人只是为了简单而重写函数,而不是开始盲目寻找实际原因:

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

void truncate_floats_to_ints(int *dst, const float *src, size_t count)
{
    const size_t  nvecs = count / 8;
    const size_t  nfloats = count & 7;
    const float  *end = src + 8 * nvecs;

    while (src < end) {
        const __m256   fvec = _mm256_load_ps(src);
        const __m256i  ivec = _mm256_cvttps_epi32(fvec);
        _mm256_store_si256((__m256i *)dst, ivec);
        src += 8;
        dst += 8;
    }

    if (nfloats) {
        __v8sf  fvec;
        __v8si  ivec;
        memcpy(&fvec, src, nfloats * 4);
        ivec = (__v8si)_mm256_cvttps_epi32(fvec);
        memcpy(dst, &ivec, nfloats * 4);
    }
}

请注意,即使在count不是8的倍数的情况下,此版本也不会访问该数组。而fvec中未使用的条目在此类中获取垃圾值(来自堆栈)例如,ivec中相应的截断值也会被忽略。如果您不喜欢这个,可以将fvec初始化为零。

另请注意,srcdst必须与32个字节对​​齐。标准C malloc()不保证这种对齐,尽管某些实现(可能是Windows?)可能。 GNU C库malloc()没有,您应该使用例如C11 aligned_alloc(32, size)其中size是32的倍数,为这样的矢量数组分配内存。

如果你不喜欢memcpy(),你可以用你自己的自定义函数替换它们,例如

#include <stdlib.h>
#include <inttypes.h>
#include <immintrin.h>

static inline void vector_copy_part(void *dst, const void *src, const size_t count)
{
    switch (count & 7) {
        case 7: ((uint32_t *)dst)[6] = ((uint32_t *)src)[6];
        case 6: ((uint64_t *)dst)[2] = ((uint64_t *)src)[2];
                ((uint64_t *)dst)[1] = ((uint64_t *)src)[1];
                ((uint64_t *)dst)[0] = ((uint64_t *)src)[0];
                break;
        case 5: ((uint32_t *)dst)[4] = ((uint32_t *)src)[4];
        case 4: ((uint64_t *)dst)[1] = ((uint64_t *)src)[1];
                ((uint64_t *)dst)[0] = ((uint64_t *)src)[0];
                break;
        case 3: ((uint32_t *)dst)[2] = ((uint32_t *)src)[2];
        case 2: ((uint64_t *)dst)[0] = ((uint64_t *)src)[0];
                break;
        case 1: ((uint32_t *)dst)[0] = ((uint32_t *)src)[0];
    }
}

void truncate_floats_to_ints(int *dst, const float *src, size_t count)
{
    const size_t  nvecs = count / 8;
    const size_t  nfloats = count & 7;
    const float  *end = src + 8 * nvecs;

    while (src < end) {
        const __m256   fvec = _mm256_load_ps(src);
        const __m256i  ivec = _mm256_cvttps_epi32(fvec);
        _mm256_store_si256((__m256i *)dst, ivec);
        src += 8;
        dst += 8;
    }

    if (nfloats) {
        __v8sf  fvec;
        __v8si  ivec;
        vector_copy_part(&fvec, src, nfloats);
        ivec = (__v8si)_mm256_cvttps_epi32(fvec);
        vector_copy_part(dst, &ivec, nfloats);
    }
}

此代码使用64位整数寄存器(具有AVX支持的体系结构上的本机寄存器大小)复制数据,并且可能为奇数元素使用一个32位整数寄存器。 (OP的代码使用循环来复制32位整数,这是非常好的 - 编译器很难进行优化,但因为它最多只有7个整数拷贝,所以不管它是否会花费任何重要的时间。)< / p>

通常,应该避免在循环中复制float(或double)值,因为这总是涉及浮点单元(这些架构上的AVX寄存器),而GCC至少不是'非常善于矢量化这样的。在支持AVX的架构上使用普通通用寄存器(相同大小的整数)复制浮点数据会生成完全相同的寄存器,而根本不涉及AVX寄存器。

GCC-5.4将上面的代码编译成一个包含八个条目的简单跳转表,并使用raxeax寄存器进行简单的移动;完全可以接受的。 truncate_floats_to_ints()中的循环只有六条指令(vcvttps2dqaddqvmovdqaaddqcmpqja)。

以上的实现都通过了我的快速测试,但总有可能潜伏着一个错误的错误等等。我不这么认为,但如果你找到了,请在评论中告诉我,这样我就可以解决。