Использование числовых типов с ускорением SIMD

SIMD (одна инструкция, несколько данных) обеспечивает поддержку оборудования для выполнения операции с несколькими частями данных параллельно с помощью одной инструкции. В .NET в пространстве имен есть набор типов System.Numerics с ускорением SIMD. Операции SIMD можно параллелизировать на уровне оборудования. Эта возможность повышает производительность векторизированных вычислений, которые широко применяются в математических, научных и графических приложениях.

Типы ускорения .NET SIMD

Типы ускорения .NET SIMD включают следующие типы:

  • Типы Vector2, Vector3 и Vector4, которые представляют векторы с 2, 3 и 4 значениями Single.

  • Два типа матрицы, Matrix3x2представляющий матрицу 3x2 и Matrix4x4матрицу, представляющую матрицу 4x4 значений Single .

  • Тип Plane , представляющий плоскость в трехмерном пространстве с помощью Single значений.

  • Тип Quaternion , представляющий вектор, используемый для кодирования трехмерных физических поворотов с помощью Single значений.

  • Тип Vector<T>, который представляет вектор указанного числового типа и реализует широкий набор операторов, оптимизированных для поддержки SIMD. Число экземпляров Vector<T> исправлено в течение времени существования приложения, но его значение Vector<T>.Count зависит от ЦП компьютера, выполняющего код.

    Примечание.

    Тип Vector<T> не включен в платформа .NET Framework. Чтобы получить доступ к этому типу, необходимо установить пакет NuGet System.Numerics.Vectors.

Типы с ускорением SIMD реализуются таким образом, что их можно использовать с аппаратным оборудованием без SIMD или компиляторами JIT. Чтобы воспользоваться инструкциями SIMD, 64-разрядные приложения должны запускаться средой выполнения, используюющей компилятор RyuJIT . Компилятор RyuJIT входит в .NET Core и в платформа .NET Framework 4.6 и более поздних версий. Поддержка SIMD предоставляется только при выборе 64-разрядных процессоров.

Как использовать SIMD?

Перед выполнением пользовательских алгоритмов SIMD можно проверка, если хост-компьютер поддерживает SIMD с помощью Vector.IsHardwareAccelerated, который возвращает Booleanзначение. Это не гарантирует, что для определенного типа включено ускорение SIMD, но является индикатором, поддерживаемым некоторыми типами.

Простые векторы

Наиболее примитивные типы с ускорением SIMD в .NET: Vector2Vector3и Vector4 типы, представляющие векторы с 2, 3 и 4 Single значениями. В приведенном ниже примере используется Vector2 для добавления двух векторов.

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

Кроме того, можно использовать векторы .NET для вычисления других математических свойств векторов, таких как Dot product, TransformClamp и т. д.

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

«Матрица»

Matrix3x2, представляющий матрицу 3x2 и Matrix4x4, представляющую матрицу 4x4. Можно использовать для вычислений, связанных с матрицами. В приведенном ниже примере показано умножение матрицы на ее корреспондентную транспонированную матрицу с помощью 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);

Вектор<T>

Это Vector<T> дает возможность использовать более длинные векторы. Число экземпляров Vector<T> исправлено, но его значение Vector<T>.Count зависит от ЦП компьютера, на котором выполняется код.

В следующем примере показано, как вычислить мудрую сумму элементов двух массивов с помощью 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;
}

Замечания

SIMD более вероятно, чтобы удалить одно узкие места и предоставить следующую, например пропускную способность памяти. В целом преимущество производительности использования SIMD зависит от конкретного сценария, и в некоторых случаях это может даже хуже, чем простой эквивалентный код, отличный от SIMD.