在 C# 中添加 long/ulong SSE 没有抛出溢出异常?

No overflow exception thrown for long/ulong SSE addition in C#?

对于非 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;
    }

下面是在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 求和并累加到十进制,已添加到我维护的 HPCsharp nuget 包(开源)中。 long[] 的版本在 SumParallel.cs 中,称为 SumToDecimalSseFasterInner()。

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