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

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

这段代码看起来非常糟糕,至少对我来说,是有很多插入/提取,它不是很优雅,可能会减慢一切。 转移时,是否有更好的方法将数据从一个区域转换为另一个区域?

buf是图像数据,16字节对齐。 宽/高是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缓存。

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

如果图像很大(大于L2尺寸)并且输出不会很快回读,请尝试使用MOVNTDQ(_mm_stream_si128)进行回写。 根据几个网站,它在SSE2,尽管你可能想要仔细检查。

SIMD教程:

  • http://www.tommesani.com/Docs.html
  • http://en.wikipedia.org/wiki/X86_instruction_listings#SSE2_instructions
  • 一些SIMD大师网站:

  • http://www.agner.org/optimize/
  • http://www.virtualdub.org/
  • http://www.pixelglow.com/

  • 在SSE3.5(又名SSSE3)出现之前,这种邻里操作对SSE来说总是很痛苦,并且引入了PALIGNR(_mm_alignr_epi8)。

    但是,如果您需要与SSE2 / SSE3向后兼容,则可以编写一个等效的宏或内联函数,它们针对SSE2 / SSE3仿真_mm_alignr_epi8,并在针对SSE3.5 / SSE4时下降到_mm_alignr_epi8。

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

    链接地址: http://www.djcxy.com/p/46625.html

    上一篇: SIMD/SSE newbie: simple image filtering

    下一篇: infer a common supertype based on a parameter value and function parameter types