Teilen über


Verwenden von SIMD-beschleunigten numerischen Typen

SIMD (Single Instruction Multiple Data) bietet Hardwareunterstützung für die Ausführung eines parallelen Vorgangs auf mehreren Datenteilen unter Verwendung einer einzelnen Anweisung. In .NET gibt es eine Reihe von SIMD-beschleunigten Typen unter dem System.Numerics-Namespace. SIMD-Vorgänge können auf Hardwareebene parallelisiert werden. Das erhöht den Durchsatz der vektorisierten Berechnungen, die in mathematischen, wissenschaftlichen und grafischen Apps üblich sind.

SIMD-beschleunigte Typen in .NET

Die SIMD-beschleunigten Typen in .NET umfassen die folgenden Typen:

  • Die Typen Vector2, Vector3 und Vector4, die Vektoren mit 2, 3 und 4 Single-Werten darstellen.

  • Die zwei Matrixtypen Matrix3x2 und Matrix4x4. Der erste Typ stellt eine 3x2-Matrix und der zweite eine 4x4-Matrix von Single-Werten dar.

  • Den Plane-Typ, der eine Ebene im dreidimensionalen Raum mithilfe von Single darstellt.

  • Den Quaternion-Typ, der einen Vektor darstellt, der zum Codieren von dreidimensionalen physischen Drehungen mithilfe von Single-Werten verwendet wird.

  • Den Vector<T>-Typ, der einen Vektor eines bestimmten numerischen Typs darstellt und mehrere Operatoren bereitstellt, die von der SIMD-Unterstützung profitieren. Die Anzahl einer Vector<T>-Instanz ist für die Lebensdauer einer Anwendung festgelegt, aber ihr Wert Vector<T>.Count hängt von der CPU des Computers ab, auf dem der Code ausgeführt wird.

    Hinweis

    Der Vector<T>-Typ ist nicht in .NET Framework enthalten. Sie müssen das NuGet-Paket System.Numerics.Vectors installieren, um Zugriff auf diesen Typ zu erhalten.

Die SIMD-beschleunigten Typen sind so implementiert, dass sie mit nicht-SIMD-beschleunigter Hardware oder JIT-Compilern verwendet werden können. Damit Sie die SIMD-Anweisungen nutzen können, müssen Ihre 64-Bit-Apps von der Runtime ausgeführt werden, die den RyuJIT-Compiler verwendet. Ein RyuJIT-Compiler ist in .NET Core und in .NET Framework 4.6 und höher enthalten. SIMD-Unterstützung wird nur für 64-Bit-Prozessoren bereitgestellt.

Verwenden von SIMD

Vor dem Ausführen von benutzerdefinierten SIMD-Algorithmen können Sie überprüfen, ob der Hostcomputer SIMD unterstützt, indem Sie Vector.IsHardwareAccelerated verwenden, wodurch ein Boolean zurückgegeben wird. Dies garantiert nicht, dass die SIMD-Beschleunigung für einen bestimmten Typ aktiviert ist, sondern ist ein Indikator dafür, dass sie von einigen Typen unterstützt wird.

Einfache Vektoren

Die einfachsten SIMD-beschleunigten Typen in .NET sind die Vector2-, Vector3- und Vector4-Typen, die Vektoren mit 2, 3 und 4 Single Werten darstellen. Im folgenden Beispiel werden mit Vector2 zwei Vektoren hinzugefügt.

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

Es ist auch möglich, .NET-Vektoren zu verwenden, um andere mathematische Eigenschaften von Vektoren wie Dot product, Transform, Clamp usw. zu berechnen.

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

Matrix

Matrix3x2 und Matrix4x4. Der erste Typ stellt eine 3x2-Matrix und der zweite eine 4x4-Matrix dar. Kann für matrixbezogene Berechnungen verwendet werden. Im folgenden Beispiel wird die Multiplikation einer Matrix mit der entsprechenden Transponierungsmatrix mithilfe von SIMD veranschaulicht.

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>

Der Vector<T> liefert die Möglichkeit, längere Vektoren zu verwenden. Die Anzahl von Vector<T>-Instanzen ist fest, aber ihr Wert Vector<T>.Count hängt von der CPU des Computers ab, auf dem der Code ausgeführt wird.

Im folgenden Beispiel wird veranschaulicht, wie die elementweise Summe von zwei Arrays mit Vector<T> berechnet wird.

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

Bemerkungen

Bei SIMD kann es vorkommen, dass ein Engpass beseitigt wird und dann der nächste entsteht, z. B. beim Arbeitsspeicherdurchsatz. Im Allgemeinen variiert der Leistungsvorteil der Verwendung von SIMD je nach spezifischem Szenario, und in einigen Fällen kann es sogar schlechter sein als einfacherer Nicht-SIMD-Code.