Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/image-processing/2.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
Performance SIMD/SSE新手:简单图像过滤_Performance_Image Processing_X86_Sse_Simd - Fatal编程技术网

Performance SIMD/SSE新手:简单图像过滤

Performance SIMD/SSE新手:简单图像过滤,performance,image-processing,x86,sse,simd,Performance,Image Processing,X86,Sse,Simd,我对SIMD/SSE非常陌生,我正在尝试做一些简单的图像过滤(模糊)。 下面的代码在水平方向上用简单的[1 2 1]加权过滤8位灰度位图的每个像素。我一次创建16个像素的总和 至少对我来说,这段代码非常糟糕的地方在于,它包含了大量的插入/提取操作,这不是很优雅,可能也会降低速度。在转换时,是否有更好的方法将数据从一个reg包装到另一个reg buf是图像数据,16字节对齐。 w/h是宽度和高度,是16的倍数 __m128i *p = (__m128i *) buf; __m128i cur1,

我对SIMD/SSE非常陌生,我正在尝试做一些简单的图像过滤(模糊)。 下面的代码在水平方向上用简单的[1 2 1]加权过滤8位灰度位图的每个像素。我一次创建16个像素的总和

至少对我来说,这段代码非常糟糕的地方在于,它包含了大量的插入/提取操作,这不是很优雅,可能也会降低速度。在转换时,是否有更好的方法将数据从一个reg包装到另一个reg

buf是图像数据,16字节对齐。 w/h是宽度和高度,是16的倍数

__m128i *p = (__m128i *) buf;
__m128i cur1, cur2, sum1, sum2, zeros, tmp1, tmp2, saved;
zeros = _mm_setzero_si128();
short shifted, last = 0, next;

// preload first row
cur1 = _mm_load_si128(p);
for (x = 1; x < (w * h) / 16; x++) {
    // unpack
    sum1 = sum2 = saved = cur1;
    sum1 = _mm_unpacklo_epi8(sum1, zeros);
    sum2 = _mm_unpackhi_epi8(sum2, zeros);
    cur1 = tmp1 = sum1;
    cur2 = tmp2 = sum2;
    // "middle" pixel
    sum1 = _mm_add_epi16(sum1, sum1);
    sum2 = _mm_add_epi16(sum2, sum2);
    // left pixel
    cur2 = _mm_slli_si128(cur2, 2);
    shifted = _mm_extract_epi16(cur1, 7);
    cur2 = _mm_insert_epi16(cur2, shifted, 0);
    cur1 = _mm_slli_si128(cur1, 2);
    cur1 = _mm_insert_epi16(cur1, last, 0);
    sum1 = _mm_add_epi16(sum1, cur1);
    sum2 = _mm_add_epi16(sum2, cur2);
    // right pixel
    tmp1 = _mm_srli_si128(tmp1, 2);
    shifted = _mm_extract_epi16(tmp2, 0);
    tmp1 = _mm_insert_epi16(tmp1, shifted, 7);
    tmp2 = _mm_srli_si128(tmp2, 2);
    // preload next row
    cur1 = _mm_load_si128(p + x);
    // we need the first pixel of the next row for the "right" pixel
    next = _mm_extract_epi16(cur1, 0) & 0xff;
    tmp2 = _mm_insert_epi16(tmp2, next, 7);
    // and the last pixel of last row for the next "left" pixel
    last = ((uint16_t) _mm_extract_epi16(saved, 7)) >> 8;
    sum1 = _mm_add_epi16(sum1, tmp1);
    sum2 = _mm_add_epi16(sum2, tmp2);
    // divide
    sum1 = _mm_srli_epi16(sum1, 2);
    sum2 = _mm_srli_epi16(sum2, 2);
    sum1 = _mm_packus_epi16(sum1, sum2);
    mm_store_si128(p + x - 1, sum1);
}
\uuum128i*p=(\uuum128i*)buf;
__m128i cur1,cur2,sum1,sum2,零,tmp1,tmp2,已保存;
零=_mm_setzero_si128();
短移位,最后一个=0,下一个;
//预加载第一行
cur1=_mm_load_si128(p);
对于(x=1;x<(w*h)/16;x++){
//打开
sum1=sum2=saved=cur1;
sum1=_mm_unplo_epi8(sum1,零);
sum2=_mm_unpachi_epi8(sum2,零);
cur1=tmp1=sum1;
cur2=tmp2=sum2;
//“中间”像素
sum1=_mm_add_epi16(sum1,sum1);
sum2=_mm_add_epi16(sum2,sum2);
//左像素
cur2=_mm_slli_si128(cur2,2);
移位=_mm_extract_epi16(cur1,7);
cur2=_mm_insert_epi16(cur2,移位,0);
cur1=_mm_slli_si128(cur1,2);
cur1=_mm_insert_epi16(cur1,last,0);
sum1=_mm_add_epi16(sum1,cur1);
sum2=_mm_add_epi16(sum2,cur2);
//右像素
tmp1=_mm_srli_si128(tmp1,2);
移位=_mm_extract_epi16(tmp2,0);
tmp1=_mm_insert_epi16(tmp1,移位,7);
tmp2=_mm_srli_si128(tmp2,2);
//预加载下一行
cur1=_mm_load_si128(p+x);
//我们需要下一行的第一个像素作为“右”像素
下一步=_mm_extract_epi16(cur1,0)&0xff;
tmp2=_mm_insert_epi16(tmp2,next,7);
//最后一行的最后一个像素表示下一个“左”像素
last=((uint16_t)_mm_extract_epi16(已保存,7))>>8;
sum1=_mm_add_epi16(sum1,tmp1);
sum2=_mm_add_epi16(sum2,tmp2);
//分开
sum1=_mm_srli_epi16(sum1,2);
sum2=_mm_srli_epi16(sum2,2);
sum1=mm_packus_epi16(sum1,sum2);
mm_store_si128(p+x-1,sum1);
}

在SSE3.5(又称SSSE3)出现之前,这种邻里操作一直是SSE的一大难题,并且引入了PALIGNR(_-mm_-aligner_-epi8)

但是,如果需要与SSE2/SSE3向后兼容,可以编写一个等效的宏或内联函数,模拟SSE2/SSE3的_mm_Aligner_epi8,并在针对SSE3.5/SSE4时延伸到_mm_Aligner_epi8


另一种方法是使用未对齐的负载来获取移位的数据-这在较旧的CPU上相对昂贵(大约是对齐负载延迟的两倍,吞吐量的一半),但这可能是可以接受的,这取决于每个负载所做的计算量。它还有一个好处,即在当前的英特尔CPU(Core i7)上,与对齐的加载相比,未对齐的加载没有任何惩罚,因此,在Core i7等处理器上,您的代码将非常高效。

我建议将相邻像素保留在SSE寄存器上。也就是说,将_mm_slli_si128/_mm_srli_si128的结果保留在SSE变量中,并消除所有插入和提取操作。我的推理是,在较旧的CPU中,插入/提取指令需要SSE单元和通用单元之间的通信,这比将计算保持在SSE内要慢得多,即使它溢出到一级缓存

这样做时,应该只有四个16位移位(_mm_slli_si128,_mm_srli_si128,不计算除数移位)。我的建议是用你的代码做一个基准测试,因为那时你的代码可能已经达到了内存带宽限制。。这意味着你不能再优化了

如果图像较大(大于L2大小),并且输出不会很快被读回,请尝试使用MOVNTDQ(_mm_stream_si128)进行写回。根据一些网站的说法,它在SSE2中,尽管你可能想仔细检查一下

SIMD教程:

一些SIMD专家网站:


我已经注意到Aligner,但正如您所怀疑的,我希望与SSE2兼容。对于x86上的SIMD,我认为SSE2是一个很好的“最低公分母”,如果单是SSE2就可以给我一个令人满意的加速,那么我就不必费心实现任何更高级的东西了。我还希望对代码可能的改进提出一般性意见。谢谢