使用 SIMD 加速數值型別
SIMD (單指令多資料) 提供硬體支援,使用單一指令以平行方式在多個資料片段上執行作業。 在 .NET 中,System.Numerics 命名空間下有一組 SIMD 加速型別。 SIMD 作業可以在硬體層級平行處理。 這能提升向量化運算 (常見於數學、科學及圖形應用程式中) 的輸送量。
.NET SIMD 加速型別
.NET SIMD 加速型別包含下列型別:
兩種矩陣類型:代表 3x2 矩陣的 Matrix3x2,以及代表 Single 值 4x4 矩陣的 Matrix4x4。
Quaternion 類型代表用來使用 Single 值編碼 3D 實體旋轉的向量。
Vector<T> 類型,其代表指定數值類型的向量,並能提供一組受益於 SIMD 支援的廣泛運算子。 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 和更新版本中。 只有在以 64 位元處理器為目標時,才會提供 SIMD 支援。
如何使用 SIMD?
在執行自訂 SIMD 演算法之前,可以使用 Vector.IsHardwareAccelerated 來檢查主機電腦是否支援 SIMD,這會傳回 Boolean。 這並不保證特定類型已啟用 SIMD 加速,但這是某些類型支援的指標。
簡單向量
.NET 中的最基本 SIMD 加速類型為 Vector2、Vector3 和 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
、Transform
、Clamp
等等。
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);
矩陣
代表 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);
向量 T<>
Vector<T> 可讓您使用較長的向量。 Vector<T> 執行個體的計數是固定的,但其值 Vector<T>.Count 會相依於執行程式碼之電腦的 CPU。
下列範例示範如何使用 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 對等程式碼執行效果更差。