【问题标题】:Can I do checked arithmetic with Vector<T>我可以用 Vector<T> 做检查算术吗
【发布时间】:2017-02-03 02:56:39
【问题描述】:

我一直在尝试使用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&lt;T&gt; 的性能优势。

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

编辑:我想了更多,想出了这个,它比未经检查的向量加法慢 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

【问题讨论】:

  • 没有,当然你可以自己检查。有了更多的上下文,我可以给出更有针对性的建议。
  • @Harold - 我添加了更多上下文。有没有判断是否溢出的有效方法?
  • 你不必分支(实际上你不能),你可以使用 ConditionalSelect。不加宽乘法很棘手,加宽很烦人,我会考虑一下。我稍后会回来更正确地回答
  • 向量整数乘法相当慢,你应该更喜欢按位运算。
  • @Harold - 非常好 - 有很大帮助。 BitwiseOr(BitwiseAnd(...), BitwiseAnd(...))。从 536 毫秒降低到 343 毫秒(与未选中的 208 毫秒相比)。

标签: .net vector simd system.numerics


【解决方案1】:

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

签名添加:

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

结果在标志中,而不是完整的掩码中。也许这就足够了,也许还不够,在这种情况下,我通常会建议右移,但Vector&lt;T&gt; 上没有右移(缺少太多东西)。不过,您可以与零进行比较。

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

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

乘法:

据我所知,没有合理的方法可以做到这一点。即使在本机 SSE 中也有点烦人,但使用 pmuldq 和一些改组并不算太糟糕。
在 C# SIMD 中,这似乎是无望的。没有 high-mul(除了 16 位整数之外,原生 SSE 也缺少,也很烦人),没有扩大乘法(无论如何也没有办法缩小结果),也没有合理的方法来提前扩大。即使您可以扩展(他们是否可以将其添加到 API 中,说真的),multiplying 64bit integers with SSE is annoying,但令人讨厌的是,使用标量算术进行它并不是一个糟糕的选择,这违背了这一点。

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

【讨论】:

  • 使用ext_prod == prod 检查溢出不是更快吗?至少它似乎在我的快速基准测试中。
  • @Rob 好吧,如果这是结果,那么是的,显然 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-22
  • 1970-01-01
  • 2014-01-11
  • 2016-11-26
  • 1970-01-01
相关资源
最近更新 更多