我需要一个 SSE shuffle 例程来避免并行减法中的负数

Posted

技术标签:

【中文标题】我需要一个 SSE shuffle 例程来避免并行减法中的负数【英文标题】:I need an SSE shuffle routine to avoid negative numbers in a parallel subtraction 【发布时间】:2013-08-02 08:05:39 【问题描述】:

我正在研究 RGB565/RGB555 Alpha 混合的 SSE2 实现,但遇到了一个我无法解决的问题。这是 C++ 中的 Alpha Blend:

#define ALPHA_BLEND_X_W(dst, src, alpha)\
    ts = src; td = dst;\
    td = ((td | (td << 16)) & RGBMask); ts = ((ts | (ts << 16)) & RGBMask);\
    td = (((((ts - td) * alpha + RGBrndX) >> 5) + td) & RGBMask);\
    dst= (td | (td >> 16));

这是用于 VBA-M 和 Kega Fusion 模拟器的过滤器插件。这已经是一个非常快速和准确的混合,但是如果我要实现我计划在我的过滤器插件中实现的所有功能,速度是至关重要的。 ts 和 td 是 32 位 INT,它允许我将绿色移出,一次性计算混合,然后将绿色移回原位。

这是我目前在 SSE 实施中所做的:

#define AlphaBlendX(s, d0, d1, d2, d3, v0, v1, v2, v3)\
    D = _mm_set_epi32(d0, d1, d2, d3);\
    S = _mm_set1_epi32(s);\
    V = _mm_set_epi16(v0, v0, v1, v1, v2, v2, v3, v3);\
    sD = _mm_slli_si128(D, 2);\
    sS = _mm_slli_si128(S, 2);\
    oD = _mm_or_si128(D, sD);\
    oS = _mm_or_si128(S, sS);\
    mD = _mm_and_si128(oD, RGB);\
    mS = _mm_and_si128(oS, RGB);\
    sub = _mm_sub_epi32(mS, mD);\
    hi = _mm_mulhi_epu16(sub, V);\
    lo = _mm_mullo_epi16(sub, V);\
    mul = _mm_or_si128(_mm_slli_si128(hi, 2), lo);\
    rnd = _mm_add_epi64(mul, RND);\
    div = _mm_srli_epi32(rnd, 5);\
    add = _mm_add_epi64(div, mD);\
    D = _mm_and_si128(add, RGB);\
    DD = _mm_srli_si128(D, 2);\
    DDD = _mm_or_si128(D, DD);\
    d0 = _mm_extract_epi16(DDD, 1); d1 = _mm_extract_epi16(DDD, 3); d2 = _mm_extract_epi16(DDD, 5); d3 = _mm_extract_epi16(DDD, 7);

即使在它处于可怕的未优化状态(所有不同的变量,而不是在每个算术运算中从 D 交换到 DD 并返回),这也是一个显着的性能改进。但是,它返回的值不正确!我非常有信心它遇到的第一个问题是减法。绝对有可能从减法运算中得到负值。

我计划的解决方案是比较四个 32 位值,然后在减法之前就地交换它们以获得减法的绝对值。我知道 _mm_cmpgt/_mm_cmplt 内在函数以及它们是如何工作的,尽管我不知道如何使用它们输出的位掩码来做我需要的事情。

对于如何在将源和目标 DWORDS 保留在其位置上的同时获得绝对值的任何可能的解决方案,我们将不胜感激。有关优化此代码的提示也很好。

【问题讨论】:

我建议你把它写成一个函数而不是一个宏。无论如何,编译器几乎肯定会内联它,但是可以通过单步调试一个函数,因此您可以查看 SSE 寄存器中的值。我不知道你为什么在最后一行使用逗号运算符... 我可能应该提到这一点,但所有的 dn 参数都是指向数组的指针。如果我将其编写为内联函数,我将不得不处理混合颜色的返回值,然后实际分别设置它们。我非常有信心第一个问题领域是减法。第二个可能的问题领域是乘法,但我已经知道如果在修复减法后仍然得到不正确的结果,我将如何处理。将逗号更改为分号。 我很确定您可以通过使用引用来解决宏的所有问题 -> 内联。无论哪种方式,编译器都可能生成等效代码。 如果你把它变成一个函数,你可以单步执行减法,看看结果是否如你所愿。 它是宏还是函数并不重要,单步执行汇编代码并在调试器中查看寄存器值是相当简单的。尤其是当您处理 SSE 时,其内在函数或多或少映射到单个指令。 【参考方案1】:

以下是使用 SSE2 获取 16(或 32 位)值的绝对值的方法:

2 的补码否定是 1 的补码后跟增量

-A == (A ^ -1) + 1;

__m128i xmmOriginal, xmmZero, xmmMask, xmmAbsolute;

// xmmOriginal is assumed to be initialized to positive/negative values

xmmZero = _mm_setzero_si128();
xmmMask = _mm_cmplt_epi16(xmmOriginal, xmmZero); // mask = FFFF where negative values are
xmmAbsolute = _mm_xor_si128(xmmMask, xmmOriginal); // bitwise invert the negative values
xmmMask = _mm_srli_epi16(xmmMask, 15); // convert mask FFFF's into 1's
xmmAbsolute = _mm_add_epi16(xmmAbsolute, xmmMask); // done

【讨论】:

以上是关于我需要一个 SSE shuffle 例程来避免并行减法中的负数的主要内容,如果未能解决你的问题,请参考以下文章

并行执行DynamoDB查询(全局二级索引的BatchGetItems)

为啥并行 SIMD/SSE/AVX 需要置换?

随机播放 16 位向量 SSE

sh 转储表,触发器和例程来分隔文件

长整数例程可以从 SSE 中受益吗?

为啥在某些 CPU 上 SSE 对齐读取 + shuffle 比未对齐读取慢,而在其他 CPU 上则不然?