Compartilhar via


Usar tipos numéricos acelerados por SIMD

O SIMD (instrução única, vários dados) fornece suporte de hardware para executar uma operação em várias partes de dados, em paralelo, usando uma única instrução. No .NET, há um conjunto de tipos acelerados por SIMD no System.Numerics namespace. As operações SIMD podem ser paralelizadas no nível do hardware. Isso aumenta a taxa de transferência das computações vetorizadas, que são comuns em aplicativos matemáticos, científicos e gráficos.

Tipos do .NET acelerados por SIMD

Os tipos acelerados pelo SIMD do .NET incluem os seguintes tipos:

  • Os tipos Vector2, Vector3 e Vector4 representam vetores com valores de 2, 3 e 4 Single.

  • Dois tipos de matriz, Matrix3x2que representa uma matriz 3x2 e Matrix4x4, que representa uma matriz de Single valores 4x4.

  • O tipo Plane, que representa um plano no espaço tridimensional usando os valores de Single.

  • O Quaternion tipo, que representa um vetor usado para codificar rotações físicas tridimensionais usando Single valores.

  • O Vector<T> tipo, que representa um vetor de um tipo numérico especificado e fornece um amplo conjunto de operadores que se beneficiam do suporte ao SIMD. A contagem de uma Vector<T> instância é fixa para o tempo de vida de um aplicativo, mas seu valor Vector<T>.Count depende da CPU do computador que executa o código.

    Observação

    O Vector<T> tipo não está incluído no .NET Framework. Você deve instalar o pacote NuGet System.Numerics.Vectors para obter acesso a esse tipo.

Os tipos acelerados por SIMD são implementados de forma que possam ser usados com hardware não acelerado por SIMD ou com compiladores JIT. Para aproveitar as instruções SIMD, seus aplicativos de 64 bits devem ser executados pelo runtime que usa o compilador RyuJIT . Um compilador RyuJIT está incluído no .NET Core e no .NET Framework 4.6 e posterior. O suporte a SIMD só é fornecido ao direcionar processadores de 64 bits.

Como usar o SIMD?

Antes de executar algoritmos SIMD personalizados, é possível verificar se o computador host dá suporte ao SIMD usando Vector.IsHardwareAccelerated, o que retorna um Boolean. Isso não garante que a aceleração de SIMD esteja habilitada para um tipo específico, mas é um indicador de que ela é suportada por alguns tipos.

Vetores simples

Os tipos mais primitivos acelerados por SIMD no .NET são Vector2, Vector3e Vector4 tipos, que representam vetores com valores 2, 3 e 4 Single . O exemplo a seguir usa Vector2 para adicionar dois vetores.

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

Também é possível usar vetores .NET para calcular outras propriedades matemáticas de vetores, como Dot product, TransformClamp e assim por diante.

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

Matriz

Matrix3x2, que representa uma matriz 3x2 e Matrix4x4, que representa uma matriz 4x4. Pode ser usado para cálculos matriciais. O exemplo a seguir demonstra a multiplicação de uma matriz para sua matriz de transposição correspondente usando 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);

Vetor<T>

Vector<T> oferece a capacidade de usar vetores mais longos. A contagem de uma Vector<T> instância é fixa, mas seu valor Vector<T>.Count depende da CPU do computador que executa o código.

O exemplo a seguir demonstra como calcular a soma em termos de elemento de duas matrizes usando 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;
}

Observações

É mais provável que o SIMD remova um gargalo e exponha o próximo, por exemplo, a taxa de transferência de memória. Em geral, o benefício de desempenho do uso do SIMD varia dependendo do cenário específico e, em alguns casos, pode até mesmo ter um desempenho pior do que o código equivalente não SIMD mais simples.