我是SIMD/SSE的新手,我正在尝试进行一些简单的图像过滤(模糊)。下面的代码过滤了一个8位灰色位图的每个像素,并在水平方向上进行简单的[1 2 1]加权。我一次创建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);
}
有帮助吗?

解决方案

我建议将相邻像素放在SSE寄存器上。也就是说,将_mm_slli_si128 / _mm_srli_si128的结果保留在SSE变量中,并消除所有插入和提取物。我的理由是,在较旧的CPU中,插入/提取说明需要在SSE单元和通用单元之间进行通信,这比将计算在SSE内的速度要慢得多,即使它溢出到L1 CACHE上。

完成此操作后,应该只有四个16位偏移(_mm_slli_si128,_mm_srli_si128, 不计算分歧班 )。我的建议是对您的代码进行基准测试,因为到那时,您的代码可能已经达到了内存带宽限制。.这意味着您无法再进行优化。

如果图像很大(大于L2大小),并且不会尽快读取输出,请尝试使用MovNTDQ(_MM_STREAM_SI128)进行回报。根据几个网站,它在SSE2中,尽管您可能需要仔细检查。

Simd教程:

一些Simd Guru网站:

其他提示

这种邻里操作始终是SSE的痛苦,直到SSE3.5(又名SSSE3)出现,并引入了Palignr(_MM_ALIGNR_EPI8)。

但是,如果您需要与SSE2/SSE3的向后兼容性,则可以编写一个等效的宏或内联函数,该功能模拟SSE2/SSE3的_mm_alignr_epi8,并在靶向SSE3.5/sse4时删除_mm_alignr_epi8。

另一种方法是使用未对准的负载来获取移位的数据 - 这对较旧的CPU(大约是延迟的两倍,是对齐负载的吞吐量的两倍),但根据您的每个负载进行的很多计算,这可能是可以接受的。它也有一个好处,即当前Intel CPU(核心i7)与对齐负载相比没有罚款,因此您的代码在Core i7上会非常有效 .

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top