SIMD アクセラレータの数値型を使用する

SIMD (Single Instruction Multiple Data) を使用すると、1 つの命令を使用して、複数のデータに対して並列で操作を実行するためのハードウェア サポートが提供されます。 .NET では、System.Numerics 名前空間の下に SIMD アクセラレータの型のセットがあります。 SIMD 操作は、ハードウェア レベルで並列化できます。 それにより、数学、科学、グラフィックス アプリで一般的な、ベクター化された計算のスループットが向上します。

.NET の SIMD アクセラレータの型

.NET の SIMD アクセラレータの型には、次の型が含まれます。

  • それぞれ 2、3、4 つの Single 値を表す Vector2 型、Vector3 型、Vector4 型。

  • 2 つのマトリックス型。3x2 行列を表す Matrix3x2Single 値の 4x4 行列を表す Matrix4x4

  • Single 値を使用して 3 次元空間の平面を表す Plane 型。

  • Single 値を使用して 3 次元物理回転をエンコードするために使用されるベクトルを表す Quaternion 型。

  • 指定の数値型のベクトルを表し、SIMD サポートが活かされる広範囲の演算子セットを提供する Vector<T> 型。 Vector<T> インスタンスの数は、アプリケーションの有効期間にわたって固定されていますが、その値 Vector<T>.Count は、コードを実行しているコンピューターの CPU によって異なります。

    注意

    Vector<T> 型は .NET Framework に含まれません。 この型にアクセスするには、System.Numerics.Vectors NuGet パッケージをインストールする必要があります。

SIMD アクセラレータの型は、非 SIMD アクセラレータのハードウェアや JIT コンパイラと共に使用できるように実装されています。 SIMD 命令を活用するには、64 ビット アプリを RyuJIT コンパイラを使用するランタイムで実行する必要があります。 RyuJIT コンパイラは、.NET Core と .NET Framework 4.6 以降に含まれています。 SIMD のサポートは、64 ビット プロセッサを対象とする場合にのみ提供されます。

SIMD の使用方法

カスタム SIMD アルゴリズムを実行する前に、Boolean を返す Vector.IsHardwareAccelerated を使用して、ホスト コンピューターが SIMD をサポートするかどうかを確認することができます。 これにより、特定の型に対して SIMD アクセラレータが有効になることは保証されませんが、一部の型でサポートされていることを示すインジケーターになります。

単純なベクター

.NET で最もプリミティブな SIMD アクセラレータの型は Vector2Vector3Vector4 型です。これは、2、3、4 つの Single 値を持つベクトルを表します。 以下の例では、Vector2 を使用して 2 つのベクトルを追加しています。

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

.NET ベクトルを使用して、Dot productTransformClamp などのベクトルの他の数学的特性を計算することもできます。

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

Matrix

3x2 行列を表す Matrix3x2 と 4x4 行列を表す Matrix4x4。 行列関連の計算に使用できます。 以下の例は、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);

Vector<T>

Vector<T> を使用すると、より長いベクトルを使用することができます。 Vector<T> インスタンスの数は固定ですが、その値 Vector<T>.Count はコードが実行されるコンピューターの CPU によって異なります。

次の例は、Vector<T> を使用して、2 つの配列の要素ごとの和を計算する方法を示しています。

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 は、メモリのスループットのように、1 つのボトルネックを取り除いても、次のものにさらされる可能性が高いです。 一般に、SIMD を使用する場合のパフォーマンス上の利点は、具体的なシナリオによって異なります。場合によっては、より単純な SIMD 以外の同等のコードよりもパフォーマンスが悪くなることもあります。