查找绝对最小值的最短方法两个数字之和并将其乘以AVX中输入的符号

问题描述

关于如何在不使用乘法的情况下为低于C的逻辑实现AVX的任何提示

for(int i = 0;i<4096;i++)
{
   out[i] = sign(inp1[i])*sign(inp2[i])*min(abs(inp1[i]),abs(inp2[i])); 
}

// inp1,inp2和out是16位寄存器。

解决方法

对于您的问题,有很短的解决方案(但不是很明显):

res = max(min(a,b),-max(a,b));

(所有最小/最大操作均已签名)

要说明为什么这样做,首先让我们进行设置

A = min(a,b); B = max(a,b);

这实际上对ab进行了排序(并排除了A>0 && B<0的情况)。现在,我们只需要区分3种情况:

A<0  && B<0:     res = -B 
A<0  && B>=0:    res = -min(-A,B) = max(A,-B)
A>=0 && B>=0:    res = A

幸运的是,第一种情况和最后一种情况也可以计算为max(A,-B),因为在第一种情况A < 0 < -B和在最后一种情况-B <= 0 <= A中。

或者,您可以问(并信任)WolframAlpha(并没有什么帮助,因为它只能评估为“假设a和b为正”),您可以绘制但这两个表达式之间的差异)


使用AVX2实现此功能(忽略加载和存储):

__m256i A = _mm256_min_epi16(a,b);
__m256i B = _mm256_max_epi16(a,b);
__m256i res = _mm256_max_epi16(A,_mm256_sub_epi16(_mm256_setzero_si256(),B));

setzero操作将在任何循环之外发生,因此对于每个数据包,有3个min / max操作和1个psub操作。在Intel CPU上,第一个执行在端口p01上,而psub在任何p015上执行,因此循环将成为p01的瓶颈,每个数据包需要1.5个周期。

如@Soonts所指出的,-B操作可能会为B=-0x8000溢出(带符号的int16没有正数0x8000)。仅a=b=-0x8000会发生这种情况。如果您希望在这种情况下输出0x7fff,则可以用饱和减法(_mm256_subs_epi16)代替减法。

,

sign(inp1[i])*sign(inp2[i])部分可以几乎完全由_mm256_sign_epi16(in1,in2)实现,并将其用作另一个vpsignw的第二操作数,以将其符号应用于min(abs,abs)结果。

psignw取反第一个操作数或零,具体取决于第二个操作数是负数还是零。Intrinsics guide)。 (我们不需要psignw的归零部分:如果任一输入为零,则其绝对值的无符号最小值将为零。但是我们必须避免,具体取决于我们如何生成输入,如果在我们的真实输入都不为零时可能发生。)

有一个错误的特殊情况:in1 = INT16_MIN = 0x8000,in2 。否定in1的结果仍然是负面的;多亏了2的补数,大多数负数都没有取反。

如果两个值之一不能为0x8000,则将其用作_mm256_sign_epi16的第一个参数,而无需执行其他操作。

@chtz提出了一种变通方法:将输入异或以得到正确的符号位值。但这将触发vpsignw对in1 == in2的归零行为,因为in1 ^ in2 == 0。您可以对异或结果使用orset1(1),以确保它不为零。

// pseudocode because the full intrinsic names are long and hard to read / type
    sign = (in1 ^ in2) | 1;
    out = psignw( min(abs1,abs2),sign);
  // operation count: XOR,OR,PSIGNW = 3 plus min(abs,abs)

在Skylake上,vpsignw可以在执行端口p0或p1上运行。 vpxorvpor之类的布尔值可以在p0,p1或p5中的任何一个上运行。 (https://uops.info/)因此,这种方法可能比另一次使用psignw的想法更好。它可以通过1条指令将两个操作数的依赖链更早地“耦合”在一起,但是即使数据来自同一操作中的另一个操作,也可能会限制吞吐量。

pabswpminuw都还需要p0 / p1,不能在p5上运行,因此选择相同数量的指令,但是使用可以使用端口5的指令可以更好地平衡Skylake后端的执行端口压力。 Zen2有点类似,布尔值可以在任何FP执行端口(0/1/2/3)上运行,但仅psignw / pabsw仅FP0 / FP3,而pminuw仅FP0 / 1/3。


另一种选择是完全避免psignw,而不是解决其归零行为:XOR,然后使用算术右移广播符号位,然后使用2的补码身份-x = ~x - (-1)进行条件求反。但这又要花更多的钱。

    sign = (in1 ^ in2) >> 15;   // pxor  psraw
    out =  (min(abs1,abs2) ^ sign) - sign;  // pxor,psubw
  // operation count: XOR,shift,XOR,SUB = 4 plus min(abs,abs)

另一种解决方法是在_mm256_or_si256(in1,_mm256_set1_epi16(1))之前插入vpsignw,以确保该值具有相同的符号,但不是INT16_MIN

// not as good as 
   sign = psignw(in1 | 1,in2);   // VPOR,VPSIGNW
   out = psignw( min(abs1,sign);
// operation count: OR,2x PSIGNW = 3 plus min(abs,abs)

算术右移1是不安全的:当输入为1时,操作数可能为零,导致输入1,2的最终输出为零


IDK(如果有一个巧妙的窍门会比每个输入上的vpabsw更好)来喂vpminuw