Partilhar via


Usar tipos numéricos acelerados por SIMD

SIMD (Single instruction, multiple data) 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 de hardware. Isso aumenta a taxa de transferência dos cálculos vetorizados, que são comuns em aplicativos matemáticos, científicos e gráficos.

Tipos acelerados por .NET SIMD

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

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

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

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

  • O Quaternion tipo, que representa um vetor que é 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 a 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 da máquina que executa o código.

    Nota

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

Os tipos acelerados por SIMD são implementados de tal forma que podem ser usados com hardware não acelerado por SIMD ou compiladores JIT. Para aproveitar as instruções do SIMD, seus aplicativos de 64 bits devem ser executados pelo tempo de execução 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 quando se destinam a processadores de 64 bits.

Como usar o SIMD?

Antes de executar algoritmos SIMD personalizados, é possível verificar se a máquina host suporta SIMD usando Vector.IsHardwareAccelerated, que retorna um Booleanarquivo . Isso não garante que a aceleração 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 types, que representam vetores com 2, 3 e 4 Single valores. O exemplo abaixo 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 relacionados à matriz. O exemplo abaixo 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>

O Vector<T> dá 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 da máquina que executa o código.

O exemplo a seguir demonstra como calcular a soma elementar 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, ele pode até ter um desempenho pior do que um código equivalente não SIMD mais simples.