SSE 双线性插值

Posted

技术标签:

【中文标题】SSE 双线性插值【英文标题】:SSE Bilinear interpolation 【发布时间】:2013-02-02 07:11:46 【问题描述】:

我正在一个紧密的循环中实现双线性插值,并尝试使用 SSE 对其进行优化,但我从中获得了零加速。

这里是代码,非 SIMD 版本使用简单的向量结构,可以定义为 struct Vec3f float x, y, z; ,并实现了乘法和加法运算符:

#ifdef USE_SIMD
    const Color c11 = pixelCache[y1 * size.x + x1];
    const Color c12 = pixelCache[y2 * size.x + x1];
    const Color c22 = pixelCache[y2 * size.x + x2];
    const Color c21 = pixelCache[y1 * size.x + x2];

    __declspec(align(16)) float mc11[4] =  1.0, c11.GetB(), c11.GetG(), c11.GetR() ;
    __declspec(align(16)) float mc12[4] =  1.0, c12.GetB(), c12.GetG(), c12.GetR() ;
    __declspec(align(16)) float mc22[4] =  1.0, c22.GetB(), c22.GetG(), c22.GetR() ;
    __declspec(align(16)) float mc21[4] =  1.0, c21.GetB(), c21.GetG(), c21.GetR() ;

    // scalars in vector form for SSE
    const float s11 = (x2-x)*(y2-y);
    const float s12 = (x2-x)*(y-y1);
    const float s22 = (x-x1)*(y-y1);
    const float s21 = (x-x1)*(y2-y);

    __declspec(align(16)) float ms11[4] = 1.0, s11, s11, s11;
    __declspec(align(16)) float ms12[4] = 1.0, s12, s12, s12;
    __declspec(align(16)) float ms22[4] = 1.0, s22, s22, s22;
    __declspec(align(16)) float ms21[4] = 1.0, s21, s21, s21;

    __asm 
        movaps xmm0, mc11
        movaps xmm1, mc12
        movaps xmm2, mc22
        movaps xmm3, mc21

        movaps xmm4, ms11
        movaps xmm5, ms12
        movaps xmm6, ms22
        movaps xmm7, ms21

        mulps xmm0, xmm4
        mulps xmm1, xmm5
        mulps xmm2, xmm6
        mulps xmm3, xmm7

        addps xmm0, xmm1
        addps xmm0, xmm2
        addps xmm0, xmm3

        movaps mc11, xmm0
    
#else
    const Vec3f c11 = toFloat(pixelCache[y1 * size.x + x1]);
    const Vec3f c12 = toFloat(pixelCache[y2 * size.x + x1]);
    const Vec3f c22 = toFloat(pixelCache[y2 * size.x + x2]);
    const Vec3f c21 = toFloat(pixelCache[y1 * size.x + x2]);

    const Vec3f colour =
            c11*(x2-x)*(y2-y) +
            c21*(x-x1)*(y2-y) +
            c12*(x2-x)*(y-y1) +
            c22*(x-x1)*(y-y1);
#endif

重新排列 asm 代码以重用寄存器(最终只有三个 xmm 寄存器)没有产生任何效果。我也尝试过使用内在函数:

// perform bilinear interpolation
const Vec3f c11 = toFloat(pixelCache[y1 * size.x + x1]);
const Vec3f c12 = toFloat(pixelCache[y2 * size.x + x1]);
const Vec3f c22 = toFloat(pixelCache[y2 * size.x + x2]);
const Vec3f c21 = toFloat(pixelCache[y1 * size.x + x2]);

// scalars in vector form for SSE
const float s11 = (x2-x)*(y2-y);
const float s12 = (x2-x)*(y-y1);
const float s22 = (x-x1)*(y-y1);
const float s21 = (x-x1)*(y2-y);

__m128 mc11 = _mm_set_ps(1.f, c11.b, c11.g, c11.r);
__m128 mc12 = _mm_set_ps(1.f, c12.b, c12.g, c12.r);
__m128 mc22 = _mm_set_ps(1.f, c22.b, c22.g, c22.r);
__m128 mc21 = _mm_set_ps(1.f, c21.b, c21.g, c21.r);

__m128 ms11 = _mm_set_ps(1.f, s11, s11, s11);
__m128 ms12 = _mm_set_ps(1.f, s12, s12, s12);
__m128 ms22 = _mm_set_ps(1.f, s22, s22, s22);
__m128 ms21 = _mm_set_ps(1.f, s21, s21, s21);

mc11 = _mm_mul_ps(mc11, ms11);
mc12 = _mm_mul_ps(mc12, ms12);
mc22 = _mm_mul_ps(mc22, ms22);
mc21 = _mm_mul_ps(mc21, ms21);

mc11 = _mm_add_ps(mc11, mc12);
mc11 = _mm_add_ps(mc11, mc22);
mc11 = _mm_add_ps(mc11, mc21);

Vec3f colour;
_mm_storeu_ps(colour.array, mc11);

但无济于事。是我遗漏了什么,还是无法在这里获得任何额外的速度?

【问题讨论】:

我不想这么说,但这绝对不是正确的做法。首先,您花费大量工作来填充向量(这纯粹是开销)。然后在计算结束时你有一个讨厌的依赖链。但从根本上说,主要问题是您使用的是结构数组打包。如果你对 SIMD 很认真,你应该考虑改用 struct-of-arrays。 好的,关于填充向量的信息我知道了,我将首先尝试重新排列数据以尊重对齐。但是您能否详细说明“计算结束时的依赖链”? 您有 3 个相互依赖的附加项。所以它们中的任何一个都不能并行完成,因为一个必须在你开始下一个之前完成。我看到你正在做某种归约——最好使用二叉树归约来完成。现在你只组合了 4 个向量。因此,无论如何重新排列它都没有太多收获。但我怀疑,从更大的角度来看,你实际上是在总结一组更大的数字。 除非你在做 HDR,否则没有理由选择浮动而不是固定的 8.8。 我实际上建议您保留 C++ 版本,但添加基于内联内在函数的 Vec3f 操作实现。这样您就可以在保持可维护代码的同时加快速度。 【参考方案1】:

为什么是浮点数? 给定 a、b、c、d 和 xerr 的打包像素 argb,yerr 在 0-256 范围内,一个简单的例子是:

// =================================================================================================================
// xs_Bilerp
// =================================================================================================================
finline uint32 xs_Bilerp (uint32 a, uint32 b, uint32 c, uint32 d, uint32 xerr, uint32 yerr)

    #define xs_rbmask    0x00ff00ff
    #define xs_agmask    0xff00ff00

    if (a==b && c==d && a==d)   return a;

    const uint32 arb        =   a & xs_rbmask;
    const uint32 crb        =   c & xs_rbmask;
    const uint32 aag        =   a & xs_agmask;
    const uint32 cag        =   c & xs_agmask;

    const uint32 rbdx1      =  (b & xs_rbmask) - arb;
    const uint32 rbdx2      =  (d & xs_rbmask) - crb;
    const uint32 agdx1      = ((b & xs_agmask)>>8) - (aag >> 8);
    const uint32 agdx2      = ((d & xs_agmask)>>8) - (cag >> 8);

    const uint32 rb1        = (arb      + ((rbdx1 * xerr) >> 8)) & xs_rbmask;
    const uint32 ag1        = (aag      + ((agdx1 * xerr)     )) & xs_agmask;
    const uint32 rbdy       = ((crb     + ((rbdx2 * xerr) >> 8)) & xs_rbmask)       - rb1;
    const uint32 agdy       = (((cag    + ((agdx2 * xerr)     )) & xs_agmask)>>8)   - (ag1 >> 8);

    const uint32 rb         = (rb1 + ((rbdy * yerr) >> 8)) & xs_rbmask;
    const uint32 ag         = (ag1 + ((agdy * yerr)     )) & xs_agmask;

    return ag | rb;

【讨论】:

以上是关于SSE 双线性插值的主要内容,如果未能解决你的问题,请参考以下文章

java 缩放算法 双线性插值,双三次插值

opencv双线性插值

双线性内插值算法

OpenCV ——双线性插值(Bilinear interpolation)

图像缩放——双线性插值算法

双线性插值算法原理 python实现