我试图通过使用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
这是不正确的。
有人可以告诉我可能是什么问题吗?
答案 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
初始化为零。
另请注意,src
和dst
必须与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将上面的代码编译成一个包含八个条目的简单跳转表,并使用rax
或eax
寄存器进行简单的移动;完全可以接受的。 truncate_floats_to_ints()
中的循环只有六条指令(vcvttps2dq
,addq
,vmovdqa
,addq
,cmpq
,ja
)。
以上的实现都通过了我的快速测试,但总有可能潜伏着一个错误的错误等等。我不这么认为,但如果你找到了,请在评论中告诉我,这样我就可以解决。