Udostępnij za pośrednictwem


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ównoleglizować na poziomie sprzętu. Zwiększa to przepływność wektoryzowanych obliczeń, które są wspólne w aplikacjach matematycznych, naukowych i graficznych.

Typy przyspieszone przez simD platformy .NET

Typy przyspieszone przez usługę .NET SIMD obejmują następujące typy:

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

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

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

  • Quaternion Typ, 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. Vector<T> Liczba wystąpień jest stała dla okresu istnienia aplikacji, ale jej wartość Vector<T>.Count zależy od procesora CPU maszyny, na którym jest uruchomiony kod.

    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 .

Przyspieszone typy SIMD są implementowane w taki sposób, że mogą być używane z nieprzyśpieszonym sprzętem lub kompilatorami JIT. 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 procesorów 64-bitowych.

Jak używać simd?

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

Wektory proste

Najbardziej pierwotne typy przyspieszone SIMD na platformie .NET to Vector2, Vector3i Vector4 typy reprezentujące wektory z wartościami 2, 3 i 4 Single . W poniższym przykładzie użyto Vector2 metody do dodania 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 obliczenia innych właściwości matematycznych wektorów, takich jak Dot product, TransformClamp i 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óry reprezentuje macierz 3x2 i Matrix4x4, która reprezentuje macierz 4x4. Może służyć do obliczeń związanych z macierzą. W poniższym przykładzie pokazano mnożenie macierzy do jej korespondencyjnych transponowanych 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>

Element Vector<T> zapewnia 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, na którym jest uruchomiony kod.

W poniższym przykładzie pokazano, jak obliczyć sumę elementów dwóch tablic przy użyciu metody 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.