Condividi tramite


Utilizzare tipi numerici accelerati con SIMD

SIMD (singola istruzione, più dati) fornisce supporto hardware per l'esecuzione di un'operazione su più parti di dati, in parallelo, usando una singola istruzione. In .NET è disponibile un set di tipi con accelerazione SIMD nello spazio dei nomi System.Numerics. Le operazioni SIMD possono essere parallelizzate a livello di hardware. Ciò aumenta la velocità effettiva dei calcoli vettorializzati, comuni nelle app matematiche, scientifiche e grafiche.

Tipi .NET con accelerazione SIMD

I tipi con accelerazione SIMD .NET includono i tipi seguenti:

  • Tipi Vector2, Vector3e Vector4 che rappresentano vettori con valori 2, 3 e 4 Single .

  • Due tipi di matrice, Matrix3x2, che rappresenta una matrice 3x2 e Matrix4x4, che rappresenta una matrice Single di valori 4x4.

  • Il tipo Plane, che rappresenta un piano nello spazio tridimensionale utilizzando i Single valori.

  • Tipo Quaternion , che rappresenta un vettore utilizzato per codificare rotazioni fisiche tridimensionali usando Single valori.

  • Tipo Vector<T> , che rappresenta un vettore di un tipo numerico specificato e fornisce un ampio set di operatori che traggono vantaggio dal supporto SIMD. Il conteggio di un'istanza Vector<T> è fisso per la durata di un'applicazione, ma il relativo valore Vector<T>.Count dipende dalla CPU del computer che esegue il codice.

    Annotazioni

    Il Vector<T> tipo non è incluso in .NET Framework. Per ottenere l'accesso a questo tipo, è necessario installare il pacchetto NuGet System.Numerics.Vectors .

I tipi con accelerazione SIMD vengono implementati in modo che possano essere usati con hardware non con accelerazione SIMD o compilatori JIT. Per sfruttare le istruzioni SIMD, le app a 64 bit devono essere eseguite dal runtime che usa il compilatore RyuJIT . Un compilatore RyuJIT è incluso in .NET Core e in .NET Framework 4.6 e versioni successive. Il supporto SIMD viene fornito solo quando è destinata ai processori a 64 bit.

Come usare SIMD?

Prima di eseguire algoritmi SIMD personalizzati, è possibile verificare se il computer host supporta SIMD usando Vector.IsHardwareAccelerated, che restituisce un oggetto Boolean. Questo non garantisce che l'accelerazione SIMD sia abilitata per un tipo specifico, ma indica che è supportata da alcuni tipi.

Vettori semplici

I tipi SIMD accelerati più primitivi in .NET sono Vector2 tipi, Vector3 tipi e Vector4 tipi, che rappresentano vettori con 2, 3 e 4 valori Single. L'esempio seguente usa Vector2 per aggiungere due vettori.

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

È anche possibile usare vettori .NET per calcolare altre proprietà matematiche di vettori, ad Dot productesempio , TransformClamp e così via.

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);

Matrice

Matrix3x2, che rappresenta una matrice 3x2 e Matrix4x4, che rappresenta una matrice 4x4. Può essere usato per i calcoli correlati alla matrice. L'esempio seguente illustra la moltiplicazione di una matrice alla matrice trasposizione corrispondente tramite 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);

Vettore<T>

Il Vector<T> offre la possibilità di usare vettori più lunghi. Il conteggio di un'istanza Vector<T> è fisso, ma il relativo valore Vector<T>.Count dipende dalla CPU del computer che esegue il codice.

Nell'esempio seguente viene illustrato come calcolare la somma a livello di elemento di due matrici usando 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;
}

Osservazioni:

SIMD è più probabile che rimuova un collo di bottiglia ed esponga il successivo, ad esempio la larghezza di banda della memoria. In generale, il vantaggio delle prestazioni dell'uso di SIMD varia a seconda dello scenario specifico e in alcuni casi può persino comportare prestazioni peggiori rispetto al codice equivalente non SIMD più semplice.