SSE内在函数 - 比较if / else优化

时间:2012-01-24 12:07:29

标签: c++ sse intrinsics

我一直在尝试优化一些处理原始像素数据的代码。目前代码的C ++实现太慢了,所以我一直试图在MSVC 2008中使用SSE内在函数(SSE / 2/3不使用4)。考虑到这是我第一次挖掘这个低,我'我们取得了一些进展。

不幸的是,我遇到了一段让我陷入困境的特定代码:

//Begin bad/suboptimal SSE code
__m128i vnMask  = _mm_set1_epi16(0x0001);
__m128i vn1     = _mm_and_si128(vnFloors, vnMask);

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++)
{
    bool bIsEvenFloor   = vn1.m128i_u16[m]==0;

    vnPxChroma.m128i_u16[m] = 
        m%2==0 
            ?
        (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m])
            :
        (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]);
}

目前,我默认使用C ++实现,因为我无法理解如何使用SSE优化这一点 - 我发现SSE内在函数用于比较有点棘手。

非常感谢任何建议/提示。

修改 一次处理单个像素的等效C ++代码将是:

short pxCl=0, pxFl=0;
short uv=0; // chroma component of pixel
short y=0;  // luma component of pixel

for(int i = 0; i < end-of-line, ++i)
{
    //Initialize pxCl, and pxFL
    //...

    bool bIsEvenI       = (i%2)==0;
    bool bIsEvenFloor   = (m_pnDistancesFloor[i] % 2)==0;

    uv = bIsEvenI ==0 
        ?
    (bIsEvenFloor ? pxCl : pxFl)
        :
    (bIsEvenFloor ? pxFl : pxCl);

    //Merge the Y/UV of the pixel;
    //...
}

基本上,我正在从4:3到16:9进行非线性边缘拉伸。

2 个答案:

答案 0 :(得分:7)

好的,所以我不知道这段代码是做什么的,但是我知道你们正在询问如何优化ternery运算符并让这部分代码仅在SSE中运行。作为第一步,我建议尝试使用整数标志和乘法来避免条件运算符。例如:

本节

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++)
{
    bool bIsEvenFloor   = vn1.m128i_u16[m]==0;      

    vnPxChroma.m128i_u16[m] = m%2==0 ?  
      (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m])  : 
      (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]); 
}

在语法上等同于此

// DISCLAIMER: Untested both in compilation and execution

// Process all m%2=0 in steps of 2
for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2)
{
    // This line could surely pack muliple u16s into one SSE2 register
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
    uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

    // This line could surely perform an SSE2 multiply across multiple registers
    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
                              iIsOddFloor * vnPxFloorChroma.m128i_u16[m]
}

// Process all m%2!=0 in steps of 2
for(int m=1; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2)
{
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
    uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxFloorChroma.m128i_u16[m] + 
                              iIsOddFloor * vnPxCeilChroma.m128i_u16[m]
}

基本上,通过分成两个循环,您将失去串行内存访问的性能增强,但会丢弃模运算和两个条件运算符。

现在你说,你注意到每个循环有两个布尔运算符以及我可能添加的乘法不是SSE内部实现。什么存储在vn1.m123i_u16 []数组中?它只是零和一个? 如果是这样,你不需要这个部分,可以取消它。如果没有,您可以将此数组中的数据规范化为仅包含0和1吗?如果vn1.m123i_u16数组只包含1和0,则此代码变为

uint16 iIsOddFloor  = vn1.m128i_u16[m]
uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

您还会注意到我没有使用SSE乘法来执行isEvenFloor * vnPx... part,也不会存储iIsEvenFlooriIsOddFloor寄存器。对不起,我不记得u16的SSE内在函数乘以/注册到顶部,但我希望这种方法很有用。您应该注意的一些优化:

// This line could surely pack muliple u16s into one SSE2 register
uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

// This line could surely perform an SSE2 multiply across multiple registers
vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
                          iIsOddFloor * vnPxFloorChroma.m128i_u16[m] 

在你发布的这段代码和我的修改中,我们仍然没有充分利用SSE1 / 2/3内在函数,但它可能会提供一些关于如何实现这一点的点(如何对代码进行矢量化) )。

最后我要说测试一切。在不再进行更改和分析之前,不加改变地运行上述代码并对其进行分析。实际的性能数字可能让您感到惊讶!


更新1

我已经通过Intel SIMD Intrinsics documentation来挑选可能对此有用的相关内在函数。具体来看看按位XOR,AND和MULT / ADD

  

__ m128数据类型
    __m128i数据类型可以包含16个8位,8个16位,4个32位或2个64位整数值。

     

__ m128i _mm_add_epi16(__ m128i a,__ m128i b)
    将a中的8个有符号或无符号16位整数添加到b

中的8个有符号或无符号16位整数中      

__ m128i _mm_mulhi_epu16(__ m128i a,__ m128i b)
    将a中的8个无符号16位整数与b中的8个无符号16位整数相乘。     打包8个无符号32位结果的高16位

     

R0 = hiword(a0 * b0)
    R1 = hiword(a1​​ * b1)
    R2 = hiword(a2 * b2)
    R3 = hiword(a3 * b3)
    ..
    R7 = hiword(a7 * b7)

     

__ m128i _mm_mullo_epi16(__ m128i a,__ m128i b)
    将a中的8个有符号或无符号16位整数与b中的8个有符号或无符号16位整数相乘。     打包8位有符号或无符号32位结果的高16位

     

R0 = loword(a0 * b0)
    R1 = loword(a1​​ * b1)
    R2 = loword(a2 * b2)
    R3 = loword(a3 * b3)
    ..
    R7 = loword(a7 * b7)

     

__ m128i _mm_and_si128(__ m128i a,__ m128i b)
    使用m2中的128位值执行m1中128位值的按位AND。

     

__ m128i _mm_andnot_si128(__ m128i a,__ m128i b)
    计算b中128位值的按位AND和128-的按位NOT   a中的位值。

     

__ m128i _mm_xor_si128(__ m128i a,__ m128i b)
     使用m2中的128位值执行m1中128位值的按位异或。

     

还来自您的代码示例以供参考

     

uint16 u1 = u2 = u3 ... = u15 = 0x1
    __m128i vnMask = _mm_set1_epi16(0x0001); //设置8个带符号的16位整数值。

     

uint16 vn1 [i] = vnFloors [i]&amp;为0x1
    __m128i vn1 = _mm_and_si128(vnFloors,vnMask); //计算a中128位值的按位AND和b中的128位值。

答案 1 :(得分:2)

安德鲁你的建议引导我走上了近乎理想的解决方案。

使用真值表和卡诺图的组合,我发现了代码

 uv = bIsEvenI ==0 
    ?
(bIsEvenFloor ? pxCl : pxFl)
    :
(bIsEvenFloor ? pxFl : pxCl);

归结为!xor(不是xor)函数。从那时起,我就能够使用SSE矢量化来优化解决方案:

//Use the mask with bit AND to check if even/odd
__m128i vnMask              = _mm_set1_epi16(0x0001);

//Set the bit to '1' if EVEN, else '0'
__m128i vnFloorsEven        = _mm_andnot_si128(vnFloors, vnMask);
__m128i vnMEven             = _mm_set_epi16
    (
        0,  //m==7
        1,
        0,
        1,
        0,
        1,
        0,  //m==1
        1   //m==0
    );


// Bit XOR the 'floor' values and 'm'
__m128i vnFloorsXorM        = _mm_xor_si128(vnFloorsEven, vnMEven);

// Now perform our bit NOT
__m128i vnNotFloorsXorM     = _mm_andnot_si128(vnFloorsXorM, vnMask);

// This is the C++ ternary replacement - using multipilaction
__m128i vnA                 = _mm_mullo_epi16(vnNotFloorsXorM, vnPxFloorChroma);
__m128i vnB                 = _mm_mullo_epi16(vnFloorsXorM, vnPxCeilChroma);

// Set our pixels - voila!
vnPxChroma                  = _mm_add_epi16(vnA, vnB);

感谢所有帮助...