在 C# 中添加 long/ulong SSE 不会引发溢出异常?

Posted

技术标签:

【中文标题】在 C# 中添加 long/ulong SSE 不会引发溢出异常?【英文标题】:No overflow exception thrown for long/ulong SSE addition in C#? 【发布时间】:2019-07-04 19:32:59 【问题描述】:

对于非 SSE 代码,如以下问题 (No overflow exception for int in C#?) 中所回答,在加法周围添加 checked 部分,在添加 Int64.MaxValue 和 1 时会引发溢出异常。但是,使用 checked 围绕 SSE 添加部分似乎没有为long[] arrLong = new long[] 5, 7, 16, Int64.MaxValue, 3, 1 ; 引发溢出异常。我相信大多数 SSE 指令都使用饱和数学,它们达到Int64.MaxValue 并且不会越过它并且永远不会转为负数。 C# 中是否有任何方法可以为 SSE 添加引发溢出异常,还是因为 CPU 可能不支持引发溢出标志而无法实现?

下面的代码显示了我使用 SSE 对 long[] 求和的 C# SSE 实现。结果是上面数组的负数,因为正数环绕并且不饱和,因为 C# 必须使用该版本的 SSE 指令(因为有两个版本:一个是环绕的,一个是饱和的)。不知道 C# 是否允许开发人员选择使用哪个版本。以下代码只有串行代码部分会引发溢出异常,但 SSE 部分不会。

    using System.Numerics;

    private static long SumSseInner(this long[] arrayToSum, int l, int r)
    
        var sumVector = new Vector<long>();
        int sseIndexEnd = l + ((r - l + 1) / Vector<long>.Count) * Vector<long>.Count;
        int i;
        for (i = l; i < sseIndexEnd; i += Vector<long>.Count)
        
            var inVector = new Vector<long>(arrayToSum, i);
            checked
            
                sumVector += inVector;
            
        
        long overallSum = 0;
        for (; i <= r; i++)
        
            checked
            
                overallSum += arrayToSum[i];
            
        
        for (i = 0; i < Vector<long>.Count; i++)
        
            checked
            
                overallSum += sumVector[i];
            
        
        return overallSum;
    

【问题讨论】:

@elgonzo:不,那不是真的。该标签可用于例如当 OP 想要一个用 C# 专门表达的答案,和/或想要明确上下文是 C# 时。但是这个问题具体是关于 C# 代码抛出的溢出异常,这是 C# 语言的一个特性。但是如果 C# 语言本身没有任何特定于 SSE 的支持,为什么我们会期望它为 SSE 代码生成溢出异常呢? C# 不处理任何 SSE 操作...仅当数据交付到本身支持它的库时才会发生。 @elgonzo:没问题。坦率地说,如果 OP 能澄清他的情况,那将会有所帮助。鉴于 C# 本身缺乏 SSE 功能,显然他正在使用 something else 来访问 SSE 功能,并且有多种可能性。根据具体情况,也许这样的“某事”可能实际上会引发溢出异常(取决于模式和/或操作)。但是这里没有足够的细节来知道是否是这样。 我相信大多数 SSE 指令都使用饱和数学 不。大多数 SSE 指令都是正常的包装二进制数学,用于元素宽度从 1 到 8 字节的加/减/乘/移位. (paddb/w/d/q)。有 有符号和无符号饱和版本可用于 add/sub,但仅适用于 8 and 16-bit elements),以及从 32 到 16 和 16 到 8 的饱和包,仅此而已。 PMADDUBSW 的横向添加也有饱和度。 无论如何,在大多数 SIMD 整数运算中,没有有效的硬件方法来检测有符号溢出。如果一种语言想要检查数学,if 当然可以使用 pcmpeq 和分支来模拟它,但这比在设置 OF 的标量 add 指令之后插入 intOjo 指令的标量等价物的开销更大有符号溢出标志。大概 C# 的 checked 东西只适用于标量数学,而不适用于 Vector&lt;&gt; 包装的内在运算。我不使用 C#,所以没有发布答案,但语言以这种方式工作似乎是完全合理的。 @PeterCordes 我想如果你真的想要检查 SIMD 添加,稳态开销是 2 条指令/添加大量添加到 检测 如果整数溢出已签名(如果未签名且 【参考方案1】:

以下是在 C# 中使用 SSE 实现 ulong 求和。我把它贴出来了,因为它比长的总结要短得多,也更容易理解。

private static decimal SumToDecimalSseFasterInner(this ulong[] arrayToSum, int l, int r)

    decimal overallSum = 0;
    var sumVector    = new Vector<ulong>();
    var newSumVector = new Vector<ulong>();
    var zeroVector   = new Vector<ulong>(0);
    int sseIndexEnd = l + ((r - l + 1) / Vector<ulong>.Count) * Vector<ulong>.Count;
    int i;

    for (i = l; i < sseIndexEnd; i += Vector<ulong>.Count)
    
        var inVector = new Vector<ulong>(arrayToSum, i);
        newSumVector = sumVector + inVector;
        Vector<ulong> gteMask = Vector.GreaterThanOrEqual(newSumVector, sumVector);         // if true then 0xFFFFFFFFFFFFFFFFL else 0L at each element of the Vector<long>
        if (Vector.EqualsAny(gteMask, zeroVector))
        
            for(int j = 0; j < Vector<ulong>.Count; j++)
            
                if (gteMask[j] == 0)    // this particular sum overflowed, since sum decreased
                
                    overallSum += sumVector[j];
                    overallSum += inVector[ j];
                
            
        
        sumVector = Vector.ConditionalSelect(gteMask, newSumVector, zeroVector);
    
    for (; i <= r; i++)
        overallSum += arrayToSum[i];
    for (i = 0; i < Vector<ulong>.Count; i++)
        overallSum += sumVector[i];
    return overallSum;

ulong[] 和 long[] 使用 SSE 求和并累积到 Decimal,以产生完全准确的结果,已添加到我维护的 HPCsharp nuget 包中(开源)。 long[] 的版本在 SumParallel.cs 中,称为 SumToDecimalSseFasterInner()。

能够使用 SSE 对 long[] 或 ulong[] 数组求和,处理 SSE 中的算术溢出,这非常酷,因为 CPU 不会为 SSE 生成溢出标志,并且以 SSE 速度执行,并且多核心!

【讨论】:

以上是关于在 C# 中添加 long/ulong SSE 不会引发溢出异常?的主要内容,如果未能解决你的问题,请参考以下文章

将 Long/ULong 转换为带有填充零的无符号十六进制字符串

C# 变量

C#基本的值类型

C#基本的值类型

c#入门笔记数据类型

c#中的数据类型