Dela via


Använda SIMD-accelererade numeriska typer

SIMD (enkel instruktion, flera data) ger maskinvarustöd för att utföra en åtgärd på flera datadelar parallellt med hjälp av en enda instruktion. I .NET finns det en uppsättning SIMD-accelererade typer under System.Numerics namnområdet. SIMD-åtgärder kan parallelliseras på maskinvarunivå. Det ökar dataflödet för vektoriserade beräkningar, som är vanliga i matematiska, vetenskapliga och grafikappar.

.NET SIMD-accelererade typer

De .NET SIMD-accelererade typerna innehåller följande typer:

  • Typerna Vector2, Vector3och Vector4 , som representerar vektorer med värdena 2, 3 och 4 Single .

  • Två matristyper, Matrix3x2, som representerar en 3x2-matris och Matrix4x4, som representerar en 4x4-matris med Single värden.

  • Typen Plane , som representerar ett plan i tredimensionellt utrymme med hjälp av Single värden.

  • Typen Quaternion , som representerar en vektor som används för att koda tredimensionella fysiska rotationer med hjälp av Single värden.

  • Typen Vector<T> , som representerar en vektor av en angiven numerisk typ och ger en bred uppsättning operatorer som har stöd för SIMD. Antalet instanser Vector<T> har åtgärdats under programmets livslängd, men dess värde Vector<T>.Count beror på processorn för den dator som kör koden.

    Kommentar

    Typen Vector<T> ingår inte i .NET Framework. Du måste installera NuGet-paketet System.Numerics.Vectors för att få åtkomst till den här typen.

SIMD-accelererade typer implementeras på ett sådant sätt att de kan användas med icke-SIMD-accelererad maskinvara eller JIT-kompilatorer. För att kunna dra nytta av SIMD-instruktioner måste dina 64-bitarsappar köras av den körning som använder RyuJIT-kompilatorn . En RyuJIT-kompilator ingår i .NET Core och i .NET Framework 4.6 och senare. SIMD-stöd tillhandahålls endast när du riktar in dig på 64-bitars processorer.

Hur använder jag SIMD?

Innan du kör anpassade SIMD-algoritmer är det möjligt att kontrollera om värddatorn stöder SIMD med hjälp Vector.IsHardwareAcceleratedav , som returnerar en Boolean. Detta garanterar inte att SIMD-acceleration är aktiverat för en viss typ, men är en indikator på att den stöds av vissa typer.

Enkla vektorer

De mest primitiva SIMD-accelererade typerna i .NET är Vector2, Vector3och Vector4 typer, som representerar vektorer med 2, 3 och 4 Single värden. Exemplet nedan använder Vector2 för att lägga till två vektorer.

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

Det går också att använda .NET-vektorer för att beräkna andra matematiska egenskaper för vektorer som Dot product, TransformClamp och så vidare.

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

Matris

Matrix3x2, som representerar en 3x2-matris och Matrix4x4, som representerar en 4x4-matris. Kan användas för matrisrelaterade beräkningar. Exemplet nedan visar multiplikation av en matris till dess korrespondent transponeringsmatris med hjälp av 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);

Vektor<T>

Ger Vector<T> möjlighet att använda längre vektorer. Antalet instanser Vector<T> är fast, men dess värde Vector<T>.Count beror på processorn för den dator som kör koden.

I följande exempel visas hur du beräknar den elementvisa summan av två matriser med hjälp av 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;
}

Kommentarer

SIMD är mer sannolikt att ta bort en flaskhals och exponera nästa, till exempel minnesdataflöde. I allmänhet varierar prestandafördelarna med att använda SIMD beroende på det specifika scenariot, och i vissa fall kan det till och med prestera sämre än enklare icke-SIMD-ekvivalent kod.