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

die*_*etr 7 performance x86 sse image-processing simd

我是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);
}
Run Code Online (Sandbox Code Playgroud)

rwo*_*ong 3

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

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

如果图像很大(大于 L2 大小)并且输出不会很快读回,请尝试使用 MOVNTDQ ( _mm_stream_si128 ) 进行写回。根据几个网站的说法,它位于 SSE2 中,但您可能需要仔细检查。

SIMD教程:

一些 SIMD 专家网站: