探索.NET平臺中的SIMD內在函式Vector

HueiFeng發表於2020-12-01

概述

Vector(向量)是一種序列式容器,事實上和陣列差不多,但它比陣列更優越。一般來說陣列不能動態擴充,因此在程式執行的時候不是浪費記憶體,就是造成越界。而Vector剛好彌補了這個缺陷,它的特徵是相當於可分配擴充的陣列(動態陣列),它的隨機訪問快,在中間插入和刪除慢,但在末端插入和刪除快。

什麼是SIMD?

SIMD是Single Instruction Multiple Data的縮寫,通常中文譯為單指令多資料流,通俗來講的話是:對多個資料執行同一個CPU指令,以達到平行運算的目的.

在GPU之前我們會通過CPU來執行該項技術來增加圖片的運算速度,例如Intel的MMX、SSE、SSE2、AVX,AMD的3DNow!等等,都是來使用SIMD
為基礎的概念,在GPU技術突飛猛進的今天,CPU的SIMD技術很少用在了圖片運算方面了,更多的是在資料庫或者其他用途上。

SIMD

它適用於機器學習、加密演算法、資料庫、和內容處理(視訊、影像、音訊編碼)中,是多執行緒不錯的選擇。

為什麼要使用SIMD

SIMD可以在多條資料通道中應用相同的操作,顯著的來提高CPU效能,通常,通道越多,效能越高(只要程式碼符合處理器的指令集)

Vector

Vector和Vector<T>類為我們提供了SIMD(單指令,多資料)指令集(SSE,AVX)的呼叫方式,可以讓我們像在C/C++中一樣去呼叫內在函式,來直接操作大多數的SIMD指令了

Vector<T>可以為任何的數字型別(sbyte, byte, short, ushort, int, uint, long, ulong, float, double),參考MSDocs

另外我們在System.Runtime.Intrinsics可享受與平臺無關的功能,也就是我們不用花費時間在不同平臺的相容方面。

順便再說一下System.Runtime.Intrinsics.X86 在這個名稱空間下,提供了SSE,SSE2,SSE3,SSSE3,SSE4.1,SSE4.2,AVX,AVX2,FMA,LZCNT,POPCNT,BMI1,BMI2,PCLMULQDQ和AES的不同Intel ISA的類的指令集。例如:在Avx類中提供了許多靜態方法,而每個AVX方法都對映到了AVX的指令
,但是在這裡有一點我們需要注意的是在這需要去通過IsSupported去檢查硬體是否支援該功能。

定義及初始化

在這之前我們可以通過Vector.IsHardwareAccelerated來判斷硬體是否支援SIMD。

if (Vector.IsHardwareAccelerated == false)
{
    //fallback to some other code;
    return;
}
//建立Vector 重複相同的值
double[] doubArray = new double[] { 1, 2, 3, 4, 4, 3, 2, 1, -1, -2, -3, -4, -5 };
Span<double> douSpan = new Span<double>(doubArray, 8, 4);
Vector<double> douZero = Vector<double>.Zero;//<0, 0, 0, 0>
Vector<float> flOne = Vector<float>.One;//<1, 1, 1, 1, 0, 0, 0, 0>
Vector<ushort> shAny = new Vector<ushort>(20);//<20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, 0, 0, 0>
Vector<double> douV = new Vector<double>(doubArray); //Will contain <1, 2, 3, 4>
Vector<double> spanduoV = new Vector<double>(douSpan); //Will contain <-1, -2, -3, -4>
Vector<double> dou2V = new Vector<double>(doubArray, 5); //Will contain <3, 2, 1, -1>
Vector<double> sumV = douV + dou2V; //Will contain <4, 4, 4, 3>

在具有AVX/AVX2功能的系統中,以上指令將建立包含4個重複的double,8個重複的float和16個重複的ushort的向量。
另外Vector可以通過陣列和Span的值進行建立

.NET中最原始的SIMD加速型別是Vector2、Vector3和Vector4型別,它們用2、3和4個單個值表示向量。下面的例子使用Vector2來新增兩個向量。

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

file

數學運算

可以使用.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); //0.55
var vResult2 = Vector2.Distance(v1, v2); //2.236068
var vResult3 = Vector2.Clamp(v1, Vector2.Zero, Vector2.One);//0.1 0.2

Vector<T>可以使用更長的向量。 Vector<T>例項的計數是固定的,但是其值Vector<T>.Count取決於執行程式碼的計算機的CPU。

下面的示例演示使用Vector<T>新增長陣列元素。

double[] SimdVectorProd(double[] left, double[] right)
{
      var offset = Vector<double>.Count;
      double[] result = new double[left.Length];
      int i = 0;
      for (i = 0; i < left.Length; i += offset)
      {
          var v1 = new Vector<double>(left, i);
          var v2 = new Vector<double>(right, i);
         (v1 * v2).CopyTo(result, i);
      }

       //remaining items
      for (; i < left.Length; ++i)
      {
          result[i] = left[i] * right[i];
      }

     return result;
}

https://docs.microsoft.com/zh-cn/dotnet/standard/simd

https://github.com/CBGonzalez/SIMDIntro

相關文章