使用 System.Numerics.Vectors 旋转二维点
Posted
技术标签:
【中文标题】使用 System.Numerics.Vectors 旋转二维点【英文标题】:Rotate 2D points using System.Numerics.Vectors 【发布时间】:2018-10-18 20:38:46 【问题描述】:我希望优化一个程序,该程序的大量计算基于大量 2D 点的旋转。我四处寻找是否可以在 C# 中使用 SIMD 进行这些计算。
我在这里找到了一个 c++ 答案,似乎可以满足我的要求,但我似乎无法使用 System.Numerics.Vectors 包将其转换为 C#。
Optimising 2D rotation
任何人都可以为我指出正确的方向吗?
下面的代码显示了没有 SIMD 的常规方法。其中 Point 是一个带有双精度 X 和 Y 的结构。
public static Point[] RotatePoints(Point[] points, double cosAngle, double sinAngle)
var pointsLength = points.Length;
var results = new Point[pointsLength];
for (var i = 0; i < pointsLength; i++)
results[i].X = (points[i].X * cosAngle) - (points[i].Y * sinAngle);
results[i].Y = (points[i].X * sinAngle) + (points[i].Y * cosAngle);
return results;
编辑:
我已经设法使用两个 Vector
private static void RotatePoints(float[] x, float[] y, float cosAngle, float sinAngle)
var chunkSize = Vector<float>.Count;
var resultX = new float[x.Length];
var resultY = new float[x.Length];
Vector<float> vectorChunk1;
Vector<float> vectorChunk2;
for (var i = 0; i < x.Length; i += chunkSize)
vectorChunk1 = new Vector<float>(x, i);
vectorChunk2 = new Vector<float>(y, i);
Vector.Subtract(Vector.Multiply(vectorChunk1, cosAngle), Vector.Multiply(vectorChunk2, sinAngle)).CopyTo(resultX, i);
Vector.Add(Vector.Multiply(vectorChunk1, sinAngle), Vector.Multiply(vectorChunk2, cosAngle)).CopyTo(resultY, i);
【问题讨论】:
看看使用Vector2
和Matrix3x2
类,我相信它们之间的乘法运算尽可能使用SIMD
不幸的是,您可以不使用 c# 做到这一点!您展示的示例使用内在函数,基本上是使用 128 位 xmm 寄存器的汇编指令,它们在 c/c++ 中工作。但是,您可以创建一个 dll(在 c/c++ 中)并从您的 c# 代码中调用它。
@AndrewWilliamson 谢谢,我已经研究了 Vector2 的可能性,它甚至有一个 Transform 方法,使用 Matrix3x2 旋转矩阵。然而,网上的例子似乎非常缺乏。所以我不知道如何将它与 Vector2 等的数组结合使用......
使用点数组对 SIMD 不利。如果您有一个包含所有 X 坐标的数组和另一个包含所有 Y 坐标的数组,就像您在编辑中添加的示例一样,它可以更有效地完成。
@harold 转换是矩阵乘法,可以使用 SIMD 轻松加速。
【参考方案1】:
编辑中添加的代码是一个好的开始,但是Vector.Multiply(Vector<float>, float)
的代码生成非常糟糕,因此应该避免使用此功能。不过,避免它是一个简单的更改,只需在循环外广播并乘以向量。我还添加了一个更合适的循环边界和“标量结语”,以防向量大小不能整齐地划分输入数组的大小。
private static void RotatePoints(float[] x, float[] y, float cosAngle, float sinAngle)
var chunkSize = Vector<float>.Count;
var resultX = new float[x.Length];
var resultY = new float[x.Length];
Vector<float> vectorChunk1;
Vector<float> vectorChunk2;
Vector<float> vcosAngle = new Vector<float>(cosAngle);
Vector<float> vsinAngle = new Vector<float>(sinAngle);
int i;
for (i = 0; i + chunkSize - 1 < x.Length; i += chunkSize)
vectorChunk1 = new Vector<float>(x, i);
vectorChunk2 = new Vector<float>(y, i);
Vector.Subtract(Vector.Multiply(vectorChunk1, vcosAngle), Vector.Multiply(vectorChunk2, vsinAngle)).CopyTo(resultX, i);
Vector.Add(Vector.Multiply(vectorChunk1, vsinAngle), Vector.Multiply(vectorChunk2, vcosAngle)).CopyTo(resultY, i);
for (; i < x.Length; i++)
resultX[i] = x[i] * cosAngle - y[i] * sinAngle;
resultY[i] = x[i] * sinAngle + y[i] * cosAngle;
【讨论】:
谢谢@harold。与没有 SIMD 的常规方法相比,我尝试对这段代码进行基准测试,SIMD 版本在我的计算机上似乎慢了大约 10 倍......我用来基准测试的 sn-p 可以在这里看到。 pastecode.xyz/view/d8c6d061 知道为什么吗? 如果我为该项目打开“首选 32 位”,我得到了我正在寻找的加速。它现在比其他方法快大约 5 倍。 @user978281 是在 32 位模式下Vector.IsHardwareAccelerated
是错误的,向量仍然有效,但只是缓慢/模拟
为什么是标量结语?为什么不让最后一个较小的块正常工作呢?
@gzak 没有更小的块,块的大小只有它的大小,如果没有足够的元素直到数组的末尾。做一个未对齐的“向量结语”,直到数组的末尾并可能与最后一个完整的块重叠一点是一种已知的技术,但更棘手,并且要求数组至少和一个块一样大(否则未对齐的块将从负索引开始)以上是关于使用 System.Numerics.Vectors 旋转二维点的主要内容,如果未能解决你的问题,请参考以下文章
System.Numerics.Vector<int> 仅部分初始化
使用 SIMD (System.Numerics) 编写向量求和函数并使其比 for 循环更快
在通用 Windows 平台中将 Vector<T> 用于 SIMD