c语言 - 使用模数 "%" 和按位计算剩余部分,"&"在 AVX2 上导致不同的结果



我正在尝试通过使用适用于 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:%dn", iLeft, i, iin1[i], ((int *)&vi0)[i], index);
iout[index] = iin1[i];
}                                   
}
}

我正在运行 iLen = 28671 的代码。前 28664 个结果是正确的。但最后 7 个结果是有问题的。 如果我在打开"iLeft = iLen%8"行的情况下编译代码,我会得到以下结果: /*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这是正确的。

另一方面,如果我在打开"iLeft = iLen&7"行的情况下编译代码,我会得到以下结果:/*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,但结果并不相同。

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

因为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 个字节。标准 Cmalloc()不能保证这种对齐,尽管某些实现(也许是 Windows?)可能会。GNU C 库malloc()没有,您应该使用 C11aligned_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 个整数副本,因此无论如何都不会花费任何大量时间。

一般来说,应该避免在循环中复制float(或double)值,因为这总是涉及浮点单元(这些架构上的AVX寄存器),而GCC至少不太擅长矢量化。在支持 AVX 的体系结构上使用普通通用寄存器(相同大小的整数)复制浮点数据会产生完全相同的寄存器,而根本不涉及 AVX 寄存器。

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

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

相关内容

  • 没有找到相关文章

最新更新