培训
使用 SIMD 加速的数字类型
SIMD(单指令多数据)为使用单指令并行对多条数据执行操作提供了硬件支持。 在 .NET 中,System.Numerics 命名空间下有一组 SIMD 加速类型。 SIMD 操作可以在硬件级别并行化。 这可以增加向量化计算的吞吐量,这在数学、科学和图形应用中很常见。
.NET SIMD 加速类型包括以下类型:
两个矩阵类型:Matrix3x2(表示 3x2 矩阵)和 Matrix4x4(表示 Single 值的 4x4 矩阵)。
Quaternion 类型,表示一个用于使用 Single 值来对三维物理旋转进行编码的向量。
Vector<T> 类型,表示指定数字类型的向量,并提供受益于 SIMD 支持的一组广泛的运算符。 Vector<T> 实例的计数在应用程序的生命周期内是固定的,但其值 Vector<T>.Count 取决于运行代码的机器的 CPU。
备注
Vector<T> 类型未包含在 .NET Framework 中。 必须安装 System.Numerics.Vectors NuGet 包才能访问此类型。
SIMD 加速的类型以这样一种方式实现:即它们可以与非 SIMD 加速的硬件或 JIT 编译器一起使用。 要利用 SIMD 指令,你的 64 位应用必须由使用 RyuJIT 编译器的运行时运行。 .NET Core 和 .NET Framework 4.6 及更高版本中包含 RyuJIT 编译器。 仅当面向 64 位处理器时才提供 SIMD 支持。
在执行自定义 SIMD 算法之前,可以使用 Vector.IsHardwareAccelerated 来检查主机是否支持 SIMD(返回一个 Boolean)。 这并不能保证为特定类型启用 SIMD 加速,但表明某些类型支持它。
.NET 中最原始的 SIMD 加速类型是 Vector2、Vector3 和 Vector4 类型,分别表示具有 2、3 和 4 个 Single 值的矢量。 下面的示例使用过 Vector2 来添加两个矢量。
var v1 = new Vector2(0.1f, 0.2f);
var v2 = new Vector2(1.1f, 2.2f);
var vResult = v1 + v2;
也可以使用 .NET 矢量来计算矢量的其他数学属性,例如 Dot product
、Transform
、Clamp
等。
var v1 = new Vector2(0.1f, 0.2f);
var v2 = new Vector2(1.1f, 2.2f);
var vResult1 = Vector2.Dot(v1, v2);
var vResult2 = Vector2.Distance(v1, v2);
var vResult3 = Vector2.Clamp(v1, Vector2.Zero, Vector2.One);
Matrix3x2 表示 3x2 矩阵,Matrix4x4 表示 4x4 矩阵。 可用于与矩阵相关的计算。 下面的示例演示了使用 SIMD 将矩阵与其对应的转置矩阵相乘。
var m1 = new Matrix4x4(
1.1f, 1.2f, 1.3f, 1.4f,
2.1f, 2.2f, 3.3f, 4.4f,
3.1f, 3.2f, 3.3f, 3.4f,
4.1f, 4.2f, 4.3f, 4.4f);
var m2 = Matrix4x4.Transpose(m1);
var mResult = Matrix4x4.Multiply(m1, m2);
Vector<T> 提供了使用更长矢量的能力。 Vector<T> 实例的计数是固定的,但其值 Vector<T>.Count 取决于运行代码的计算机的 CPU。
以下示例演示如何使用 Vector<T> 计算两个数组的元素总和。
double[] Sum(double[] left, double[] right)
{
if (left is null)
{
throw new ArgumentNullException(nameof(left));
}
if (right is null)
{
throw new ArgumentNullException(nameof(right));
}
if (left.Length != right.Length)
{
throw new ArgumentException($"{nameof(left)} and {nameof(right)} are not the same length");
}
int length = left.Length;
double[] result = new double[length];
// Get the number of elements that can't be processed in the vector
// NOTE: Vector<T>.Count is a JIT time constant and will get optimized accordingly
int remaining = length % Vector<double>.Count;
for (int i = 0; i < length - remaining; i += Vector<double>.Count)
{
var v1 = new Vector<double>(left, i);
var v2 = new Vector<double>(right, i);
(v1 + v2).CopyTo(result, i);
}
for (int i = length - remaining; i < length; i++)
{
result[i] = left[i] + right[i];
}
return result;
}
SIMD 更有可能消除一个瓶颈但又暴露下一个瓶颈,例如内存吞吐量。 一般来说,使用 SIMD 的性能优势取决于具体的场景,在某些情况下,它的性能甚至可能比更简单的非 SIMD 等效代码更差。