我一直在尝试使用Vector来使用HW来并行化整数运算。有没有办法用向量运算启用溢出检查?
一个例子是将两列(等长阵列)的int一起添加。此处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阵列)
答案 0 :(得分:1)
使用从Hacker的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中,这似乎毫无希望。没有高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。