Бөлісу құралы:


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

SIMD (одна инструкция, несколько данных) обеспечивает поддержку оборудования для выполнения операции с несколькими частями данных параллельно с помощью одной инструкции. В .NET имеется набор типов, ускоренных с помощью SIMD, принадлежащих пространству имен System.Numerics. Операции 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 во время выполнения, используйте Vector.IsHardwareAccelerated. Если это свойство возвращает true, по крайней мере некоторые API используют операции с аппаратным ускорением SIMD. Если возвращается false, ни один API не ускоряется аппаратно.

Как использовать 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.