我一直在尝试优化一些处理原始像素数据的代码。目前代码的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进行非线性边缘拉伸。
答案 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
,也不会存储iIsEvenFloor
和iIsOddFloor
寄存器。对不起,我不记得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)
中的8个有符号或无符号16位整数中
将a中的8个有符号或无符号16位整数添加到b__ 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);
感谢所有帮助...