使用 SIMD 加速的数字类型

SIMD(单指令多数据)提供硬件支持,以单个指令并行处理多个数据元素。 在 .NET 中,命名空间下 System.Numerics 有一组 SIMD 加速类型。 可以在硬件级别对 SIMD 操作进行并行处理。 这会增加矢量化计算的吞吐量,这些计算在数学、科学和图形应用中很常见。

.NET SIMD 加速类型

.NET SIMD 加速类型包括以下类型:

  • Vector2Vector3Vector4 类型表示具有 2、3 和 4 个 Single 值的向量。

  • 两种矩阵类型,Matrix3x2表示一个 3x2 矩阵,Matrix4x4表示一个以Single值构成的 4x4 矩阵。

  • 类型 Plane ,表示使用 Single 值的三维空间中的平面。

  • 类型 Quaternion ,表示用于使用 Single 值对三维物理旋转进行编码的向量。

  • Vector<T> 类型表示指定数值类型的向量,并提供一组广泛的运算符,这些运算符受益于 SIMD 支持。 在应用程序的生存期内,Vector<T> 实例的计数是固定的,但其值 Vector<T>.Count 取决于运行代码的计算机的 CPU。

    注释

    类型 Vector<T> 不包括在 .NET Framework 中。 必须安装 System.Numerics.Vectors NuGet 包才能访问此类型。

SIMD 加速类型通过这样的方式实现,使其可用于非 SIMD 加速硬件或 JIT 编译器。 若要利用 SIMD 指令,必须使用 RyuJIT 编译器的运行时运行 64 位应用。 RyuJIT 编译器包含在 .NET Core 和 .NET Framework 4.6 及更高版本中。 只有在针对 64 位处理器时,才提供 SIMD 支持。

如何使用 SIMD?

在执行自定义 SIMD 算法之前,可以使用 Vector.IsHardwareAccelerated 来检查主机是否支持 SIMD,它将返回一个 Boolean。 这不能保证为特定类型启用 SIMD 加速,但表示某些类型支持 SIMD 加速。

简单向量

.NET 中大多数基元 SIMD 加速类型是 Vector2Vector3以及 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 productTransformClamp等等。

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 矩阵,表示 Matrix4x44x4 矩阵。 可用于与矩阵相关的计算。 下面的示例演示如何使用 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);

矢量<T>

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 等效代码更差。