SIMD像素对比度:像素与其邻居之间的差异总和(uint16_t颜色分量,浮点数)?

时间:2015-12-23 22:04:25

标签: image-processing assembly x86 sse avx

使用SIMD /汇编器以绝对值(最大差值)减去2 uint16并将结果加(+ =)到浮点数的最佳方法是什么?

与此C' ish示例类似

c0 += fabs((float)a0 - (float)b0);  // C is Float accumulator, a+b pixels

其中a和b是无符号16位字,c是浮点数。只有一个字 - >浮动转换而不是3.

Thee应用程序正在处理尽可能多的完整RGB像素的原始16位无符号int图像数据。

可能在Skylake Xeon E3-1275 v5上使用AVX2 / SSE4.2?

5分钟的评论限制?无法保存或重新编辑???

  
    你确定需要漂浮吗?     Uint16不能累加超过1的减法。我想做一个邻域对比计算,所以我需要总结至少8个差异。在具有D深度的邻域中存在(2D + 1)^ 2-1个邻居。我也希望能够解决uint32太小的差异。我觉得花车看起来也比较顺畅。

  

以下是有关已经工作的内容以及我希望如何改进的更多背景信息。

为了澄清,我目前的C代码计算固定家庭像素与8个或更多邻居之间的每个通道差异。它有一个5深的嵌套循环结构: Y行然后是图像中每个像素的X-cols(3600万) 频道,R。G& B是loop3 循环4和5用于邻域的行和列。

每个HOME像素

    清除R,G和B累加器     对于每个邻居,
        将abs(home_red - nabr_red)添加到red_float_accumulator         绿色和蓝色相同     将累计值复制到主存储器

我的下一步是将通道移至5级,并与SIMD同时进行所有3次减法,R,G和B.每个MMX寄存器有48位/像素和128位可用,一次可以完成2位而不是1位。

在Skylake Xeon的AVX2中有512位寄存器,可以完成10个。我正在寻找一个良好的策略来平衡复杂性和性能,并了解有关这些向量操作的更多信息。

我需要R&G和B累加器用于每个" home"像素。然后将RGB移动到"浮动图像"与uint16 / channel RAW,RGB文件具有相同的XY分辨率。对每个像素进行相同的对比度计算。

2 个答案:

答案 0 :(得分:3)

关于无符号整数绝对值的问题,来自Justin W's answer:做两次饱和减法。一个结果是零,另一个是绝对值。将它们与逻辑OR组合。这样做,在打开16b整数到32b整数或浮点数之前,是最便宜的。

我们肯定想在从word到dpack解包之前减去,所以只有一个值可以解包。进行单个非饱和减法然后将其排序(例如,通过在转换为FP之后将符号位屏蔽为零)将不起作用。存在范围问题:签名的int16_t不能保持两个uint16_t值之间的差异的整个范围。 UINT16_MAX将环绕并看起来像-1的差异 - >另外,它还有一个缺点,即要求符号扩展解包。

与AVX2一样,解压缩到不同的矢量宽度是主要的问题,因为某些指令的内部行为以及其他指令的跨车道行为。

vpunpcklwd在每个128b通道内解包,与vpackusdw匹配。 vpmovzxwd ymm, xmm没有单指令反转,因此假设您可以按照这种方式排列浮点数,我会使用punpck指令。 (对于PMOVZX,你不能直接从上半部分开始。你必须vextracti128 / vpmovzx。)

#include <immintrin.h>

// untested
__m256 add_pixdiff(__m256 c[2], __m256i a, __m256i b)
{
    __m256i ab = _mm256_subs_epu16(a, b);  // 0        or abs(a-b)
    __m256i ba = _mm256_subs_epu16(b, a);  // abs(a-b) or 0
    __m256i abs_ab_diffs = _mm256_or_si256(ab, ba);

    __m256i lo_uints = _mm256_unpacklo_epi16(abs_ab_diffs, _mm256_setzero_si256());
    __m256i hi_uints = _mm256_unpackhi_epi16(abs_ab_diffs, _mm256_setzero_si256());

    __m256 lo_floats = _mm256_cvtepi32_ps(lo_uints);
    __m256 hi_floats = _mm256_cvtepi32_ps(hi_uints);

    // use load and store intrinsics if the data might not be aligned.
    c[0] = _mm256_add_ps(c[0], lo_floats);
    c[1] = _mm256_add_ps(c[1], hi_floats);
    return c[0];
 }

它是compiles exactly like you'd expect,关于godbolt。要在循环中使用,最好手动内联它。你不希望一个愚蠢的编译器实际使用内存中的数组来为两个浮点向量做引用调用。我只是把它包装在一个函数中,以保持简单并隐藏加载/存储。

请注意,uint16输入的一个向量会生成两个float结果向量。我们可以一次对整数向量128b进行操作,但是一次做256b意味着我们得到3个insn(不计算负载)+2个解压缩的结果,而不是6个insn + 2 pmovzx。这里有一个相当大的并行度:两个减法可以并行发生,并且有两个解包和转换的依赖链。 (Skylake只有一个shuffle端口,所以你不能同时得到两个解包。它是一个内线指令with a latency of 1c,而vpmovzx ymm, xmm有一个延迟3,就像其他的交叉指令一样。如果你需要保持浮点数与整数的顺序相同,那么并行性在那里很重要,而不是最后重新打包回相同的顺序。)

Vector Sky的延迟在Skylake上增加到4(从以前的英特尔设计中的3个),但吞吐量增加到每个时钟两个。 (它在FMA单元上运行。没错,skylake的FMA延迟到4c)。

我假设你实际上并不想要一个SAD(一个累加器用于所有差异),因为你用标量形式写了c0而不是c

如果你想要一个SAD(绝对差值之和),那就容易多了,你应该在整数域中积累。使用相同的sub两种方式使用无符号饱和技巧,或者将它们放在一起,解压缩到32bit int,然后添加到向量累加器。最后做水平和。

答案 1 :(得分:0)

第二个答案,用于矢量化嵌套循环的实际问题,进行邻居对比度计算,而不仅仅是第一个问题所涉及的最基本的构建块。

诀窍是在你的5个嵌套循环中选择正确的循环来进行矢量化。

// probably convert the image to planar outside the loop
// rather than dealing with packed components on the fly,
// since you read the same src pixel many times
// (comparing it to a different dst pixel each time).

for (dst_row) {
    for (dst_col) {
        for (r, g, b) {  
            // if you convert to planar, make this the outer-most loop for cache reasons
            for (src_row) {
                for (src_col) {
                    accumulate stuff;
                }
            }
        }
        result[drow][dcol].r = sum over some range of red   src rows X cols;
        result[drow][dcol].g = sum over some range of green src rows X cols;
        result[drow][dcol].b = sum over some range of blue  src rows X cols;
    }
}

您可能认为应该为单个像素或单个像素的单个组件矢量化accumulate stuff。这需要进行水平求和(将向量累加器的所有元素减少为单个标量和)。

更好的方法是一次累加16个目标列的结果(AVX / AVX2:8个打包的单精度浮点数的两个256b向量,因为从解包一个16b数据向量得到两个值得结果的向量)。 FP可能不比32位整数差很多,如果您使用FMA指令可能会更好。

内部两个循环的每次迭代,您都会累积16个不同src像素的结果。 (或者src组件,如果你不在循环外转换为平面。)这会在内部循环中创建一个 lot 的局部性。您一次沿16b滑动256b窗口(或者一次48b而不是平面),而不是一次256b,因此数据会被重复使用。

最重要的是,你根本不需要进行横向操作。在for (dst_col)循环的每次迭代中,结果向量最终保存16个完整结果元素,准备存储到result[drow][dcol..dcol+16]中。 (虽然它位于点,您可以使用vpermd来移动通道并理顺vpunpcklwd给出的排序,与vpmovzxwd相比较如果你关心你的浮动数组以严格正确的顺序存储。)

打包到平面会使角落情况变得更加容易,并且将浮动对比度存储为平面的数组结构格式,这样可以更容易地对某些内容进行矢量化,例如将r,g和b的对比度相加像素。 addpshaddps快得多,因此您希望在一个向量中有8个r值,在另一个向量中需要8个g值,而不是{rgb,rgb的两个向量,x}有两个浪费的元素或其他东西。