我想在c ++中尽可能快地缩小图像。 This article描述了如何有效地将32位rgb图像平均降低50%。它很快,看起来很好。
我尝试使用sse intrinsics修改该方法。无论是否启用SSE,下面的代码都可以使用。但令人惊讶的是,加速可以忽略不计。
任何人都可以看到改进SSE代码的方法。创建变量shuffle1和shuffle2的两条线似乎是两个候选者(使用一些聪明的移位或类似)。
/*
* Calculates the average of two rgb32 pixels.
*/
inline static uint32_t avg(uint32_t a, uint32_t b)
{
return (((a^b) & 0xfefefefeUL) >> 1) + (a&b);
}
/*
* Calculates the average of four rgb32 pixels.
*/
inline static uint32_t avg(const uint32_t a[2], const uint32_t b[2])
{
return avg(avg(a[0], a[1]), avg(b[0], b[1]));
}
/*
* Calculates the average of two rows of rgb32 pixels.
*/
void average2Rows(const uint32_t* src_row1, const uint32_t* src_row2, uint32_t* dst_row, int w)
{
#if !defined(__SSE)
for (int x = w; x; --x, dst_row++, src_row1 += 2, src_row2 += 2)
* dst_row = avg(src_row1, src_row2);
#else
for (int x = w; x; x-=4, dst_row+=4, src_row1 += 8, src_row2 += 8)
{
__m128i left = _mm_avg_epu8(_mm_load_si128((__m128i const*)src_row1), _mm_load_si128((__m128i const*)src_row2));
__m128i right = _mm_avg_epu8(_mm_load_si128((__m128i const*)(src_row1+4)), _mm_load_si128((__m128i const*)(src_row2+4)));
__m128i shuffle1 = _mm_set_epi32( right.m128i_u32[2], right.m128i_u32[0], left.m128i_u32[2], left.m128i_u32[0]);
__m128i shuffle2 = _mm_set_epi32( right.m128i_u32[3], right.m128i_u32[1], left.m128i_u32[3], left.m128i_u32[1]);
_mm_store_si128((__m128i *)dst_row, _mm_avg_epu8(shuffle1, shuffle2));
}
#endif
}
答案 0 :(得分:8)
在通用寄存器和SSE寄存器之间传输数据非常慢,所以你应该避免这样的事情:
__m128i shuffle1 = _mm_set_epi32( right.m128i_u32[2], right.m128i_u32[0], left.m128i_u32[2], left.m128i_u32[0]);
__m128i shuffle2 = _mm_set_epi32( right.m128i_u32[3], right.m128i_u32[1], left.m128i_u32[3], left.m128i_u32[1]);
借助相应的随机操作,在SSE寄存器中对值进行随机播放。
这应该是你要找的东西:
__m128i t0 = _mm_unpacklo_epi32( left, right ); // right.m128i_u32[1] left.m128i_u32[1] right.m128i_u32[0] left.m128i_u32[0]
__m128i t1 = _mm_unpackhi_epi32( left, right ); // right.m128i_u32[3] left.m128i_u32[3] right.m128i_u32[2] left.m128i_u32[2]
__m128i shuffle1 = _mm_unpacklo_epi32( t0, t1 ); // right.m128i_u32[2] right.m128i_u32[0] left.m128i_u32[2] left.m128i_u32[0]
__m128i shuffle2 = _mm_unpackhi_epi32( t0, t1 ); // right.m128i_u32[3] right.m128i_u32[1] left.m128i_u32[3] left.m128i_u32[1]
答案 1 :(得分:4)
如果SSE内在函数几乎没有差异,那么代码可能受内存带宽的限制。
在你的代码中有很多的加载和存储,(_mm_set_epi32
是一个负载以及明显的负载),实际工作量不大。
如果加载/存储在运行时占主导地位,则没有多少花哨的指令可以为您节省时间。在高流水线和重新排序指令的现代处理器上,它可能在保持整个处理器忙于代码的非SSE版本方面做得很好。
您可以通过多种方式验证这种情况。最简单的方法是测量算法的实际吞吐量与内存的加载/存储速度的比较。您可能还会注意到一些不同之处,不仅仅是改变实现方式,还会改变输入的大小,因为输入超出了每个级别的处理器缓存的大小而急剧增加。
答案 2 :(得分:4)
主要问题是使用_mm_set_epi32
进行改组 - 与大多数SSE内在函数不同,这不会直接映射到单个SSE指令 - 在这种情况下它会生成大量的标量代码。引擎盖,并使数据在存储器,通用寄存器和SSE寄存器之间移动。相反,请考虑使用适当的SSE shuffle内在函数。
第二个问题是,相对于加载和存储的数量,您的计算量非常小。这将导致代码受带宽限制而非计算限制,即使使用理想的SSE代码,您也可能看不到显着的性能提升。考虑在循环中组合更多操作,以便在缓存中对数据执行更多操作。