我可以用 Vector<T> 做校验算术吗

Can I do checked arithmetic with Vector<T>

我一直在尝试 Vector 使用硬件来并行化整数运算。有什么方法可以启用矢量操作的溢出检查吗?

一个例子是将两列(等长数组)的整数加在一起。这里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 的自动溢出检查慢,并且可能抵消使用 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 数组)

使用从 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),,如此烦人以至于用标量算术来做它不是一个坏的选择,这违背了这一点。

所以我建议不要在 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 中。