当前位置:  开发笔记 > 编程语言 > 正文

将8个字符从内存加载到__m256变量中作为压缩单精度浮点数

如何解决《将8个字符从内存加载到__m256变量中作为压缩单精度浮点数》经验,为你挑选了1个好方法。

我正在优化图像上的高斯模糊算法,我想用下面的代码替换__m256内部变量中浮点缓冲区[8]的用法.哪一系列说明最适合此任务?

// unsigned char *new_image is loaded with data
...
  float buffer[8];

  buffer[x ]      = new_image[x];       
  buffer[x + 1] = new_image[x + 1]; 
  buffer[x + 2] = new_image[x + 2]; 
  buffer[x + 3] = new_image[x + 3]; 
  buffer[x + 4] = new_image[x + 4]; 
  buffer[x + 5] = new_image[x + 5]; 
  buffer[x + 6] = new_image[x + 6]; 
  buffer[x + 7] = new_image[x + 7]; 
 // buffer is then used for further operations
...

//What I want instead in pseudocode:
 __m256 b = [float(new_image[x+7]), float(new_image[x+6]), ... , float(new_image[x])];

Peter Cordes.. 6

如果您使用的是AVX2,则可以使用PMOVZX将字符零扩展到256b寄存器中的32位整数.从那里,转换为浮动可以就地发生.

; rsi = new_image
VPMOVZXBD   ymm0,  [rsi]   ; or SX to sign-extend  (Byte to DWord)
VCVTDQ2PS   ymm0, ymm0     ; convert to packed foat

我用SSE2(作为浮点数)缩放字节像素值(y = ax + b)的答案?可能是相关的.如果使用AVX2进行vpmovzxbd ymm,xmm,那么后面的数据包返回部分是半技巧的,因为它们在通道内工作,不像vpshufb ymm.

由于这个答案提交了两个gcc错误:

SSE/AVX movq load(_mm_cvtsi64_si128)未折叠成pmovzx

_mm256_shuffle_epi8在32位模式下没有x86的固有特性.(TODO:还为clang/LLVM报告这个?)


只有AVX1而不是AVX2,你应该这样做:

VPMOVZXBD   xmm0,  [rsi]
VPMOVZXBD   xmm1,  [rsi+4]
VINSERTF128 ymm0, ymm0, xmm1, 1   ; put the 2nd load of data into the high128 of ymm0
VCVTDQ2PS   ymm0, ymm0     ; convert to packed float.  Yes, works without AVX2

你当然永远不需要一个浮点数组,只需要vpmovzx ymm,mem向量.

实际上,我无法找到一种方法来实现这一点,既有安全的内在函数(避免在所需的8B之外加载vpmovzx xmm,mem)和最佳(使用良好的代码_mm_loadl_epi64).

使用SSE4.1 _mm256_cvtepu8_epi32/ vpshufb作为加载没有固有的,只有vpmovsx/zx ymm源操作数.但是,他们只读取实际使用的数据量.与之不同vpmovzx,您可以在页面的最后一个8B上使用它而不会出现错误.(即使使用非AVX版本,也可以使用未对齐的地址).

有一个内在的vpmovzx,但gcc 5.3没有看透它仍然将负载折叠到内存操作数vpmovzxbd.因此该函数被编译为3条指令.clang 3.6将movq折叠为pmovzx的内存操作数,但是clang 3.5.1不会.ICC13也提供最佳代码.

所以这是我提出的邪恶解决方案.不要用这个,uint8_t不好.

#if !defined(__OPTIMIZE__)
// Making your code compile differently with/without optimization is a TERRIBLE idea
// great way to create Heisenbugs that disappear when you try to debug them.
// Even if you *plan* to always use -Og for debugging, instead of -O0, this is still evil
#define USE_MOVQ
#endif

__m256 load_bytes_to_m256(uint8_t *p)
{
#ifdef  USE_MOVQ  // compiles to an actual movq then movzx ymm, xmm with gcc8.3 -O3
    __m128i small_load = _mm_loadl_epi64( (const __m128i*)p);
#else  // USE_LOADU // compiles to a 128b load with gcc -O0, potentially segfaulting
    __m128i small_load = _mm_loadu_si128( (const __m128i*)p );
#endif

    __m256i intvec = _mm256_cvtepu8_epi32( small_load );
    //__m256i intvec = _mm256_cvtepu8_epi32( *(__m128i*)p );  // compiles to an aligned load with -O0
    return _mm256_cvtepi32_ps(intvec);
}

启用USE_MOVQ后,packssdw/packuswb(v5.3.0)会发出

load_bytes_to_m256(unsigned char*):
        vmovq   xmm0, QWORD PTR [rdi]
        vpmovzxbd       ymm0, xmm0
        vcvtdq2ps       ymm0, ymm0
        ret

愚蠢vpmovzx是我们想要避免的.如果你让它使用__m256版本,它将做出很好的优化代码.


使用内在函数编写仅限AVX1的版本对读者来说是一种无趣的练习.你问过"指令",而不是"内在函数",这是内在函数存在差距的地方.VPMOVZXBD ymm,[mem]IMO,必须使用以避免可能从越界地址加载,这是愚蠢的.我希望能够根据它们映射到的指令来考虑内在函数,加载/存储内在函数通知编译器有关对齐保证或缺少对齐保证.必须使用内在函数来执行我不想要的指令是非常愚蠢的.

另请注意,如果您正在查看英特尔insn参考手册,则movq有两个单独的条目:

movd/movq,可以将整数寄存器作为src/dest操作数的版本(_mm_loadl_epi64(或vpmovzx*)用于(V)MOVQ xmm,r/m64).在那里你可以找到可以接受64位整数的内在函数.

movq:可以有两个xmm寄存器作为操作数的版本.这是MMXreg - > MMXreg指令的扩展,它也可以像MOVDQU一样加载/存储.它的操作码pmovzxbq xmm, word [mem](vmovq)为vpmovzx.这里列出的唯一内在函数是_mm_loadl_epi64在复制时将向量的高64b归零.

这真是愚蠢.gcc甚至没有定义_mm_loadu_si12832位目标. -O3当然在32位模式下是不可编码的,因为它依赖于VEX.W(或非AVX编码的REX前缀),并且可以将64位寄存器编码为源,而不是64位存储器位置.你可以使用内在的加载到MMX寄存器,然后mmx - > xmm,然后-O0,但它可能不会优化通过mmx寄存器的反弹.

ICC13确实定义vmovdqu为32位,但MOVQ m64, %xmm它编译为2x pmovsx+pmovzx.它管理使用__m128i__m128i,不过,(单独的测试功能),即使在32位模式.所以它不会模仿脑死亡的方式.



1> Peter Cordes..:

如果您使用的是AVX2,则可以使用PMOVZX将字符零扩展到256b寄存器中的32位整数.从那里,转换为浮动可以就地发生.

; rsi = new_image
VPMOVZXBD   ymm0,  [rsi]   ; or SX to sign-extend  (Byte to DWord)
VCVTDQ2PS   ymm0, ymm0     ; convert to packed foat

我用SSE2(作为浮点数)缩放字节像素值(y = ax + b)的答案?可能是相关的.如果使用AVX2进行vpmovzxbd ymm,xmm,那么后面的数据包返回部分是半技巧的,因为它们在通道内工作,不像vpshufb ymm.

由于这个答案提交了两个gcc错误:

SSE/AVX movq load(_mm_cvtsi64_si128)未折叠成pmovzx

_mm256_shuffle_epi8在32位模式下没有x86的固有特性.(TODO:还为clang/LLVM报告这个?)


只有AVX1而不是AVX2,你应该这样做:

VPMOVZXBD   xmm0,  [rsi]
VPMOVZXBD   xmm1,  [rsi+4]
VINSERTF128 ymm0, ymm0, xmm1, 1   ; put the 2nd load of data into the high128 of ymm0
VCVTDQ2PS   ymm0, ymm0     ; convert to packed float.  Yes, works without AVX2

你当然永远不需要一个浮点数组,只需要vpmovzx ymm,mem向量.

实际上,我无法找到一种方法来实现这一点,既有安全的内在函数(避免在所需的8B之外加载vpmovzx xmm,mem)和最佳(使用良好的代码_mm_loadl_epi64).

使用SSE4.1 _mm256_cvtepu8_epi32/ vpshufb作为加载没有固有的,只有vpmovsx/zx ymm源操作数.但是,他们只读取实际使用的数据量.与之不同vpmovzx,您可以在页面的最后一个8B上使用它而不会出现错误.(即使使用非AVX版本,也可以使用未对齐的地址).

有一个内在的vpmovzx,但gcc 5.3没有看透它仍然将负载折叠到内存操作数vpmovzxbd.因此该函数被编译为3条指令.clang 3.6将movq折叠为pmovzx的内存操作数,但是clang 3.5.1不会.ICC13也提供最佳代码.

所以这是我提出的邪恶解决方案.不要用这个,uint8_t不好.

#if !defined(__OPTIMIZE__)
// Making your code compile differently with/without optimization is a TERRIBLE idea
// great way to create Heisenbugs that disappear when you try to debug them.
// Even if you *plan* to always use -Og for debugging, instead of -O0, this is still evil
#define USE_MOVQ
#endif

__m256 load_bytes_to_m256(uint8_t *p)
{
#ifdef  USE_MOVQ  // compiles to an actual movq then movzx ymm, xmm with gcc8.3 -O3
    __m128i small_load = _mm_loadl_epi64( (const __m128i*)p);
#else  // USE_LOADU // compiles to a 128b load with gcc -O0, potentially segfaulting
    __m128i small_load = _mm_loadu_si128( (const __m128i*)p );
#endif

    __m256i intvec = _mm256_cvtepu8_epi32( small_load );
    //__m256i intvec = _mm256_cvtepu8_epi32( *(__m128i*)p );  // compiles to an aligned load with -O0
    return _mm256_cvtepi32_ps(intvec);
}

启用USE_MOVQ后,packssdw/packuswb(v5.3.0)会发出

load_bytes_to_m256(unsigned char*):
        vmovq   xmm0, QWORD PTR [rdi]
        vpmovzxbd       ymm0, xmm0
        vcvtdq2ps       ymm0, ymm0
        ret

愚蠢vpmovzx是我们想要避免的.如果你让它使用__m256版本,它将做出很好的优化代码.


使用内在函数编写仅限AVX1的版本对读者来说是一种无趣的练习.你问过"指令",而不是"内在函数",这是内在函数存在差距的地方.VPMOVZXBD ymm,[mem]IMO,必须使用以避免可能从越界地址加载,这是愚蠢的.我希望能够根据它们映射到的指令来考虑内在函数,加载/存储内在函数通知编译器有关对齐保证或缺少对齐保证.必须使用内在函数来执行我不想要的指令是非常愚蠢的.

另请注意,如果您正在查看英特尔insn参考手册,则movq有两个单独的条目:

movd/movq,可以将整数寄存器作为src/dest操作数的版本(_mm_loadl_epi64(或vpmovzx*)用于(V)MOVQ xmm,r/m64).在那里你可以找到可以接受64位整数的内在函数.

movq:可以有两个xmm寄存器作为操作数的版本.这是MMXreg - > MMXreg指令的扩展,它也可以像MOVDQU一样加载/存储.它的操作码pmovzxbq xmm, word [mem](vmovq)为vpmovzx.这里列出的唯一内在函数是_mm_loadl_epi64在复制时将向量的高64b归零.

这真是愚蠢.gcc甚至没有定义_mm_loadu_si12832位目标. -O3当然在32位模式下是不可编码的,因为它依赖于VEX.W(或非AVX编码的REX前缀),并且可以将64位寄存器编码为源,而不是64位存储器位置.你可以使用内在的加载到MMX寄存器,然后mmx - > xmm,然后-O0,但它可能不会优化通过mmx寄存器的反弹.

ICC13确实定义vmovdqu为32位,但MOVQ m64, %xmm它编译为2x pmovsx+pmovzx.它管理使用__m128i__m128i,不过,(单独的测试功能),即使在32位模式.所以它不会模仿脑死亡的方式.

推荐阅读
可爱的天使keven_464
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有