在 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
指令之后插入 intO
或 jo
指令的标量等价物的开销更大有符号溢出标志。大概 C# 的 checked
东西只适用于标量数学,而不适用于 Vector<>
包装的内在运算。我不使用 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 不会引发溢出异常?的主要内容,如果未能解决你的问题,请参考以下文章