2

我一直在尝试使用Vector来使用 HW 来并行化整数运算。有没有办法通过向量操作启用溢出检查?

一个例子是将两列(等长数组)的整数加在一起。这里c=a+b表示c[0] = a[0] + b[0],c[1] = a[1] + b[1]等。

我想我可以做这样的事情:

overflow[i] = b[i] >= 0 ? c[i] < a[i] : c[i] >= a[i];

但这(分支)可能比 .Net 的自动溢出检查慢,并且可能会抵消使用 .Net 的性能优势Vector<T>

我们还想优化我们最常用的运算:乘法、减法,以及较小程度的整数除法。

编辑:我想了更多,想出了这个,它比未经检查的向量加法慢 2.5 倍。似乎有很多额外的开销。

    public Vector<int> Calc(Vector<int> a, Vector<int> b)
    {
        var result = a + b;
        var overflowFlag = Vector.GreaterThan(b, Vector<int>.Zero) * Vector.LessThan(result,a)
            + Vector.LessThan(b,Vector<int>.Zero) * Vector.GreaterThan(result, a);

        // It makes no sense to add the flags to the result, but haven't decided what to do with them yet, 
        // and don't want the compiler to optimise the overflow calculation away
        return result + overflowFlag;
    }

时序:(4k 次迭代添加一对 100k 数组)

  • 正常添加:618ms
  • 正常检查添加:1092ms
  • 矢量添加:208ms
  • 矢量检查添加:536ms
4

1 回答 1

1

使用从 Hacker's Delight(第 2 章,溢出检测部分)借来的一些技巧,这里有一些溢出谓词(未经测试):

签名补充:

var sum = a + b;
var ovf = (sum ^ a) & (sum ^ b);

结果是在标志中,而不是完整的面具。也许这已经足够了,也许不是,在这种情况下,我通常会建议右移,但没有右移Vector<T>(缺少太多东西)。不过,您可以与零进行比较。

无符号添加:为了完整性?

var sum = a + b;
var ovf = Vector.LessThan(sum, a);

乘法:

据我所知,没有合理的方法可以做到这一点。即使在本机 SSE 中也有点烦人,但经过pmuldq一些改组,这还不错。
在 C# SIMD 中,这似乎是无望的。没有 high-mul(除了 16 位整数之外,原生 SSE 也缺少,也很烦人),没有扩大乘法(无论如何也没有办法缩小结果),也没有合理的方法来提前扩大。即使您可以扩大范围(他们是否可以将其添加到 API 中,认真地),将 64 位整数与 SSE 相乘也很烦人,因此使用标量算术进行运算并不是一个坏选择,这与这一点无关。

所以我建议不要在 SIMD 中这样做,至少不要在 C# 中这样做。

这并不一定意味着您使用内置的溢出检测。虽然如果溢出是一个致命错误是合适的,但如果它很常见并且是预期的,并且您只希望布尔标志中的溢出状态,那么它会非常缓慢。在这种情况下,您可以使用:

有符号乘法:

long ext_prod = (long)a * b;
int prod = (int)ext_prod;
bool ovf = (prod >> 31) != (int)(ext_prod >> 32);

无符号乘法:

ulong ext_prod = (ulong)a * b;
uint prod = (uint)ext_prod;
bool ovf = (ext_prod >> 32) != 0;

在 SIMD 中,它的工作方式基本相同,即测试高半部分是否填充了符号的副本(在无符号情况下定义为零),但扩大使其在本机 SIMD 中令人讨厌,而在 C# SIMD 中则无望。

于 2016-09-26T22:47:21.307 回答