Usa tipos numéricos acelerados por SIMD

SIMD (instrucción única, varios datos) proporciona compatibilidad de hardware para realizar una operación en varios fragmentos de datos, en paralelo, mediante una sola instrucción. En .NET, hay un conjunto de tipos acelerados por SIMD en el System.Numerics espacio de nombres . Las operaciones SIMD pueden paralelizarse a nivel hardware. Eso aumenta el rendimiento de los cálculos vectorizados, que son comunes en aplicaciones matemáticas, científicas y gráficas.

Tipos acelerados por SIMD .NET

Los tipos acelerados para SIMD para .NET incluyen los siguientes:

  • Los tipos Vector2, Vector3 y Vector4, que representan vectores con los valores Single 2, 3 y 4.

  • Dos tipos de matriz: Matrix3x2, que representa una matriz de 3x2, y Matrix4x4, que representa una matriz de 4x4 de Single valores.

  • El tipo Plane, que representa un plano en un espacio tridimensional usando valores Single.

  • El tipo Quaternion, que representa un vector que se usa para codificar rotaciones físicas tridimensionales usando valores Single.

  • El tipo Vector<T>, que representa un vector de un tipo numérico especificado y proporciona un amplio conjunto de operadores que aprovechan la compatibilidad con SIMD. El recuento de una instancia se fija durante la vigencia de una Vector<T> aplicación, pero su valor Vector<T>.Count depende de la CPU de la máquina que ejecuta el código.

    Nota

    El Vector<T> tipo no está incluido en .NET Framework. Debe instalar el paquete NuGet System.Numerics.Vectors para acceder a este tipo.

Los tipos acelerados para SIMD se implementan de tal forma que se pueden utilizar con hardware no acelerado para SIMD o compiladores JIT. Para aprovechar las instrucciones de SIMD, las aplicaciones de 64 bits las debe ejecutar el entorno de ejecución que usa el compilador RyuJIT. Un compilador RyuJIT se incluye en .NET Core y en .NET Framework 4.6 y versiones posteriores. La compatibilidad con SIMD solo se proporciona cuando tiene como destino procesadores de 64 bits.

¿Cómo usar la red SIMD?

Antes de ejecutar algoritmos SIMD personalizados, es posible comprobar si la máquina host admite SIMD mediante Vector.IsHardwareAccelerated, que devuelve un Boolean. Esto no garantiza que la aceleración de SIMD esté habilitada para un tipo específico, pero es un indicador de que es compatible con algunos tipos.

Vectores simples

Los tipos acelerados por SIMD más primitivos de .NET son Vector2, Vector3y Vector4 tipos, que representan vectores con 2, 3 y 4 Single valores. En el ejemplo siguiente se usa Vector2 para agregar dos vectores.

var v1 = new Vector2(0.1f, 0.2f);
var v2 = new Vector2(1.1f, 2.2f);
var vResult = v1 + v2;

También es posible usar vectores de .NET para calcular otras propiedades matemáticas de vectores como Dot product, Transform, etc 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);

Matriz

Matrix3x2, que representa una matriz de 3x2, y Matrix4x4, que representa una matriz de 4x4. Se puede usar para cálculos relacionados con matrices. En el ejemplo siguiente se muestra la multiplicación de una matriz a su matriz de transposición correspondiente mediante 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>

El Vector<T> proporciona la capacidad de usar vectores más largos. El recuento de una instancia Vector<T> es fijo, pero su valor Vector<T>.Count depende de la CPU de la máquina que ejecuta el código.

En el ejemplo siguiente se muestra cómo calcular la suma por elementos de dos matrices mediante 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;
}

Comentarios

Es más probable que SIMD quite un cuello de botella y exponga el siguiente, por ejemplo, el rendimiento de memoria. En general, la ventaja de rendimiento del uso de SIMD varía en función del escenario específico y, en algunos casos, puede incluso funcionar peor que el código equivalente distinto de SIMD más simple.