Korzystanie z przyspieszonych typów liczbowych SIMD

SimD (pojedyncza instrukcja, wiele danych) zapewnia obsługę sprzętu do wykonywania operacji na wielu fragmentach danych, równolegle przy użyciu jednej instrukcji. Na platformie .NET znajduje się zestaw typów przyspieszonych przez simD w System.Numerics przestrzeni nazw. Operacje SIMD można zrównoleglić na poziomie sprzętu. Zwiększa to przepływność wektoryzowanych obliczeń, które są powszechne w aplikacjach matematycznych, naukowych i graficznych.

Typy przyspieszone przez simD platformy .NET

Typy przyspieszone przez simD platformy .NET obejmują następujące typy:

  • Typy Vector2, Vector3i Vector4 reprezentujące wektory z wartościami 2, 3 i 4 Single .

  • Dwa typy macierzy , Matrix3x2które reprezentują macierz 3x2 i Matrix4x4, które reprezentują macierz Single 4x4 wartości.

  • Typ Plane , który reprezentuje płaszczyznę w trójwymiarowej przestrzeni przy użyciu Single wartości.

  • Typ Quaternion , który reprezentuje wektor używany do kodowania trójwymiarowych rotacji fizycznych przy użyciu Single wartości.

  • Typ Vector<T> , który reprezentuje wektor określonego typu liczbowego i zapewnia szeroki zestaw operatorów, które korzystają z obsługi SIMD. Liczba Vector<T> wystąpień jest stała przez cały okres istnienia aplikacji, ale jej wartość Vector<T>.Count zależy od procesora CPU maszyny z uruchomionym kodem.

    Uwaga

    Typ Vector<T> nie jest uwzględniony w programie .NET Framework. Aby uzyskać dostęp do tego typu, należy zainstalować pakiet NuGet System.Numerics.Vectors .

Typy przyspieszone przez usługę SIMD są implementowane w taki sposób, że mogą być używane z nieprzyśpieszonym sprzętem lub kompilatorami JIT bez karty SIMD. Aby korzystać z instrukcji SIMD, aplikacje 64-bitowe muszą być uruchamiane przez środowisko uruchomieniowe korzystające z kompilatora RyuJIT . Kompilator RyuJIT jest dołączony do platformy .NET Core i w programie .NET Framework 4.6 lub nowszym. Obsługa simD jest zapewniana tylko w przypadku 64-bitowych procesorów.

Jak używać simd?

Przed wykonaniem niestandardowych algorytmów SIMD można sprawdzić, czy maszyna hosta obsługuje kartę SIMD przy użyciu metody Vector.IsHardwareAccelerated, która zwraca Booleanwartość . Nie gwarantuje to włączenia przyspieszania SIMD dla określonego typu, ale jest wskaźnikiem obsługiwanym przez niektóre typy.

Proste wektory

Najbardziej pierwotne typy przyspieszone simD na platformie .NET to Vector2, Vector3i Vector4 typy, które reprezentują wektory z wartościami 2, 3 i 4 Single . W poniższym przykładzie użyto Vector2 funkcji dodawania dwóch wektorów.

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

Można również użyć wektorów platformy .NET do obliczania innych właściwości matematycznych wektorów, takich jak Dot product, Transformi Clamp tak dalej.

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

Macierz

Matrix3x2, która reprezentuje macierz 3x2 i Matrix4x4, która reprezentuje macierz 4x4. Można użyć do obliczeń związanych z macierzą. W poniższym przykładzie pokazano mnożenie macierzy do jej korespondenta transponowania macierzy przy użyciu 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);

Wektor<T>

Daje Vector<T> możliwość używania dłuższych wektorów. Liczba wystąpień Vector<T> jest stała, ale jej wartość Vector<T>.Count zależy od procesora CPU maszyny z uruchomionym kodem.

W poniższym przykładzie pokazano, jak obliczyć sumę mądry elementów dwóch tablic przy użyciu polecenia 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;
}

Uwagi

SimD jest bardziej prawdopodobne, aby usunąć jedno wąskie gardło i uwidocznić następny, na przykład przepływność pamięci. Ogólnie rzecz biorąc, korzyści z wydajności korzystania z simD różnią się w zależności od konkretnego scenariusza, a w niektórych przypadkach może nawet działać gorzej niż prostszy kod równoważny inny niż SIMD.