好的,所以我不知道这段代码在做什么,但是我知道您在问如何优化三元运算符并让这部分代码仅在 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]);
}
在语法上等价于 this
// 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[] 数组中存储了什么?只有零和一吗?
如果是这样,您不需要这部分并且可以取消它。如果没有,您可以将此数组中的数据标准化为只有零和一吗?如果 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 挑选出可能对此有用的相关内在函数。具体看一下按位异或、与和多/加
__m128 数据类型
__m128i 数据类型可以保存 16 个 8 位、8 个 16 位、4 个 32 位或两个 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=低字(a0 * b0)
R1=低字(a1 * b1)
R2=低字(a2 * b2)
R3=低字(a3 * b3)
..
R7=低字(a7 * b7)
__m128i _mm_and_si128(__m128i a, __m128i b)
将 m1 中的 128 位值与 m2 中的 128 位值进行按位与。
__m128i _mm_andnot_si128(__m128i a, __m128i b)
计算 b 中的 128 位值的按位与和 128- 的按位非
a中的位值。
__m128i _mm_xor_si128(__m128i a, __m128i b)
将 m1 中的 128 位值与 m2 中的 128 位值进行按位异或。
还来自您的代码示例以供参考
uint16 u1 = u2 = u3 ... = u15 = 0x1
__m128i vnMask = _mm_set1_epi16(0x0001); // 设置 8 个带符号的 16 位整数值。
uint16 vn1[i] = vnFloors[i] & 0x1
__m128i vn1 = _mm_and_si128(vnFloors, vnMask); // 计算 a 中的 128 位值和 b 中的 128 位值的按位与。