Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/71.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 每秒将字节快速复制到新内存区域_C_Performance_Sse_Memcpy_Sse2 - Fatal编程技术网

C 每秒将字节快速复制到新内存区域

C 每秒将字节快速复制到新内存区域,c,performance,sse,memcpy,sse2,C,Performance,Sse,Memcpy,Sse2,我需要一种快速的方法将每秒钟的字节复制到一个新的malloc'd内存区域。 我有一个带有RGB数据的原始图像,每个通道16位(48位),我想创建一个每个通道8位(24位)的RGB图像 有没有比复制字节更快的方法? 我对SSE2了解不多,但我认为SSE/SSE2是可能的。您的RGB数据是压缩的,因此我们实际上不必关心像素边界。问题是数组中每隔一个字节就要打包一次。(至少在图像的每一行中;如果使用16或32B的行跨距,则填充可能不是整数像素。) 这可以通过使用SSE2、AVX或AVX2洗牌有效地完成

我需要一种快速的方法将每秒钟的字节复制到一个新的malloc'd内存区域。 我有一个带有RGB数据的原始图像,每个通道16位(48位),我想创建一个每个通道8位(24位)的RGB图像

有没有比复制字节更快的方法?
我对SSE2了解不多,但我认为SSE/SSE2是可能的。

您的RGB数据是压缩的,因此我们实际上不必关心像素边界。问题是数组中每隔一个字节就要打包一次。(至少在图像的每一行中;如果使用16或32B的行跨距,则填充可能不是整数像素。)

这可以通过使用SSE2、AVX或AVX2洗牌有效地完成。(还有AVX512BW,使用AVX512VBMI可能会更多,但第一个AVX512VBMI CPU可能不会有非常高效的CPU)


您可以使用SSSE3
pshufb
来获取所需的字节,但它只是一个单输入随机播放,将为您提供8个字节的输出。一次存储8个字节比一次存储16个字节需要更多的存储指令。 (由于Haswell只有一个洗牌端口,因此每个时钟的洗牌吞吐量只有一个,这也会成为Intel CPU洗牌吞吐量的瓶颈)。(也可以考虑2x<代码> pSUFB< <代码> +代码> POR <代码>来给16B存储,并且可以在RyZin上很好。使用2种不同的混洗控制向量,一个将结果放在低64个B中,一个将结果放在高的64个B中。参见) 相反,使用()可能是一种胜利。但是,由于它会饱和,而不是丢弃不需要的字节,因此必须向它输入要保留在每个16位元素的低位字节中的数据

在您的例子中,这可能是每个RGB16组件的高字节,丢弃每个颜色组件的8个最低有效位。i、 e.
\u mm\u srli\u epi16(v,8)
要将每个16位元素中的高位字节归零,请使用
\u mm\u和_si128(v,\u mm\u set1\u epi16(0x00ff))
。(在这种情况下,不要使用所有关于使用未对齐负载替换其中一个班次的内容;这是一个简单的情况,您应该只使用两个and来填充PACKUS。)

这或多或少就是gcc和clang在
-O3
自动矢量化的方式。除非他们都搞砸了,浪费了重要的指令(,)。尽管如此,让他们使用SSE2(x86-64的基线)自动矢量化,或者使用NEON(ARM)或其他工具自动矢量化,是一种很好的安全方法,可以在手动矢量化时获得一些性能,而不会引入错误。除了编译器bug之外,它们生成的任何东西都将正确实现此代码的C语义,适用于任何大小和对齐方式:

// gcc and clang both auto-vectorize this sub-optimally with SSE2.
// clang is *really* sub-optimal with AVX2, gcc no worse
void pack_high8_baseline(uint8_t *__restrict__ dst, const uint16_t *__restrict__ src, size_t bytes) {
  uint8_t *end_dst = dst + bytes;
  do{
     *dst++ = *src++ >> 8;
  } while(dst < end_dst);
}
//gcc和clang都使用SSE2自动以最佳方式向量化此子对象。
//使用AVX2时,叮当声是次优的,gcc不会更差
无效数据包高8基线(uint8*uuuu限制\uuuuuDST,常数uint16*uu限制\uuuuuu限制\uuuuuuuu字节){
uint8_t*end_dst=dst+字节;
做{
*dst++=*src++>>8;
}而(dst
请参阅此版本和更高版本的代码+asm

// Compilers auto-vectorize sort of like this, but with different
// silly missed optimizations.
// This is a sort of reasonable SSE2 baseline with no manual unrolling.
void pack_high8(uint8_t *restrict dst, const uint16_t *restrict src, size_t bytes) {
  // TODO: handle non-multiple-of-16 sizes
  uint8_t *end_dst = dst + bytes;
  do{
     __m128i v0 = _mm_loadu_si128((__m128i*)src);
     __m128i v1 = _mm_loadu_si128(((__m128i*)src)+1);
     v0 = _mm_srli_epi16(v0, 8);
     v1 = _mm_srli_epi16(v1, 8);
     __m128i pack = _mm_packus_epi16(v0, v1);
     _mm_storeu_si128((__m128i*)dst, pack);
     dst += 16;
     src += 16;  // 32 bytes, unsigned short
  } while(dst < end_dst);
}
//编译器自动矢量化类似于这样,但具有不同的
//愚蠢的错过了优化。
//这是一种合理的SSE2基线,无需手动展开。
无效数据包高8(uint8*restrict dst、const uint16*restrict src、大小字节){
//TODO:处理16个大小中的非多个
uint8_t*end_dst=dst+字节;
做{
__m128i v0=_mm_loadu_si128((u m128i*)src);
__m128i v1=_mm_loadu_si128((u m128i*)src)+1;
v0=_mm_srli_epi16(v0,8);
v1=_mm_srli_epi16(v1,8);
__m128i封装=毫米封装epi16(v0,v1);
_mm_-storeu-si128((_-m128i*)dst,包装;
dst+=16;
src+=16;//32字节,无符号短
}而(dst

但在许多微体系结构中,向量移位吞吐量限制在每个时钟1个(天湖之前的英特尔、AMD推土机/瑞森)。此外,在AVX512之前没有load+shift asm指令,因此很难通过管道获得所有这些操作。(即,我们很容易在前端遇到瓶颈。)

我们可以从偏移一个字节的地址加载,而不是移位,这样我们想要的字节就在正确的位置上了。屏蔽我们想要的字节具有良好的吞吐量,特别是在AVX中,编译器可以将load+和折叠成一条指令。如果输入是32字节对齐的,并且我们只对奇数向量执行这种偏移加载技巧,那么我们的加载将永远不会跨越缓存线边界。对于循环展开,这可能是跨多个CPU的SSE2或AVX(不带AVX2)的最佳选择

// take both args as uint8_t* so we can offset by 1 byte to replace a shift with an AND
// if src is 32B-aligned, we never have cache-line splits
void pack_high8_alignhack(uint8_t *restrict dst, const uint8_t *restrict src, size_t bytes) {
  uint8_t *end_dst = dst + bytes;
  do{
     __m128i v0 = _mm_loadu_si128((__m128i*)src);
     __m128i v1_offset = _mm_loadu_si128(1+(__m128i*)(src-1));
     v0 = _mm_srli_epi16(v0, 8);
     __m128i v1 = _mm_and_si128(v1_offset, _mm_set1_epi16(0x00FF));
     __m128i pack = _mm_packus_epi16(v0, v1);
     _mm_store_si128((__m128i*)dst, pack);
     dst += 16;
     src += 32;  // 32 bytes
  } while(dst < end_dst);
}
//将两个参数都取为uint8\u t*,这样我们就可以偏移1字节,用AND替换移位
//如果src是32B对齐的,我们就不会有缓存线拆分
void pack_high8_alignhack(uint8_t*restrict dst,const uint8_t*restrict src,size_t字节){
uint8_t*end_dst=dst+字节;
做{
__m128i v0=_mm_loadu_si128((u m128i*)src);
__m128i v1_偏移量=_mm_载荷u_si128(1+(u m128i*)(src-1));
v0=_mm_srli_epi16(v0,8);
__m128i v1=_mm_和_si128(v1_偏移,_mm_设置1_epi16(0x00FF));
__m128i封装=毫米封装epi16(v0,v1);
_mm_store_si128((_m128i*)dst,包装;
dst+=16;
src+=32;//32字节
}而(dst
在没有AVX的情况下,内部循环每16B结果向量接受6条指令(6个UOP)。(使用AVX仅为5,因为负载折叠到和中)。因为这在前端完全是瓶颈,所以循环展开帮助很大
gcc-O3-funroll循环
对于这个手动矢量化的版本来说非常好,尤其是使用
gcc-O3-funroll循环-march=sandybridge
来启用AVX

使用AVX时,可能值得同时使用
执行
v0
v1
,以减少前端瓶颈,但代价是缓存线拆分。(偶尔也会出现分页符)。但可能不是,取决于uarch,以及