C#的GPU加速方法
前往我的主頁以獲得更好的閱讀體驗C#的GPU加速方法 - DearXuan的主頁https://blog.dearxuan.com/2021/08/13/C-%E7%9A%84GPU%E5%8A%A0%E9%80%9F%E6%96%B9%E6%B3%95/
本文將通過C#調用dll的方法來實現并發計算
在VS2019里新建動態鏈接庫項目,在pch.h里定義函數
// pch.h: 這是預編譯標頭文件。 // 下方列出的文件僅編譯一次,提高了將來生成的生成性能。 // 這還將影響 IntelliSense 性能,包括代碼完成和許多代碼瀏覽功能。 // 但是,如果此處列出的文件中的任何一個在生成之間有更新,它們全部都將被重新編譯。 // 請勿在此處添加要頻繁更新的文件,這將使得性能優勢無效。#ifndef PCH_H #define PCH_H// 添加要在此處預編譯的標頭 #include "framework.h" extern "C" _declspec(dllexport) void Sum(int* s,int a[],int b[],int length);#endif //PCH_H在pch.cpp里實現該函數
// pch.cpp: 與預編譯標頭對應的源文件#include "pch.h" #include <amp.h>// 當使用預編譯的頭時,需要使用此源文件,編譯才能成功。using namespace Concurrency;extern "C" _declspec(dllexport) void Sum(int* s, int a[], int b[],int length) {array_view<const int, 1> aArray(length, a);array_view<const int, 1> bArray(length, b);array_view<int, 1> sum(length, s);parallel_for_each(sum.extent,[=](index<1> idx) restrict(amp) {sum[idx] = aArray[idx] + bArray[idx];}); }該函數接收4個參數,分別用來儲存結果,a數組,b數組,數組長度,并將a和b數組相加,結果儲存在s里面。
array_view表示包含在一個容器中的數據的N維視圖,各項參數的含義如下:
????????const int:類型,
????????1:維數
????????aArray:array_view的實例
????????length:長度
????????a:數據源
如果是二維數組,則要改成下面的形式
array_view<const int, 2> aArray(width,height, a);parallel_for_each語句能夠進行并發計算,index<1>指idx是一維的,如果是二維數組,需要改成index<2>,此時idx相當于(i,j),通過idx[0]和idx[1]獲得行號和列號
例如
int row = idx[0]; int col = idx[1];aArray[idx]和aArray(row,col)是等效的
將上述代碼生成dll,并放在C#程序的目錄下
導入剛剛寫的dll
[DllImport("Dll1.dll", EntryPoint = "Sum", CallingConvention = CallingConvention.Cdecl)] public static extern void Sum(IntPtr s,int[] a, int[] b,int length);生成隨機數數組,求和
static void Main(string[] args) {const int size = 100;int[] s = new int[size];int[] a = new int[size];int[] b = new int[size];Random random = new Random();for(int i = 0; i < size; i++){a[i] = random.Next(0, 100);b[i] = random.Next(100, 200);}unsafe{IntPtr p = Marshal.UnsafeAddrOfPinnedArrayElement(s, 0);Sum(p, a, b, size);}for(int i = 0; i < size; i++){Console.WriteLine(a[i] + "+" + b[i] + "=" + s[i]);}Console.ReadLine(); }使用StopWatch類來計算耗時(命名空間System.Diagnostics)
Stopwatch watch1 = new Stopwatch(); watch1.Start(); for(int i = 0; i < size; i++) {s[i] = a[i] + b[i]; } watch1.Stop(); Console.WriteLine("CPU耗時:" + watch1.Elapsed.TotalMilliseconds); Stopwatch watch2 = new Stopwatch(); watch2.Start(); Sum(p, a, b, size); watch2.Stop(); Console.WriteLine("GPU耗時:" + watch2.Elapsed.TotalMilliseconds);由于加載dll本身需要時間,所以在計時之前需要先調用一次Sum函數。
測試代碼是計算4億個數的和,可以看到GPU計算比CPU計算少了300毫秒,但是CPU在循環2億次的情況下居然僅僅比GPU多了300毫秒,這是因為GPU無法從內存讀取數據,需要把數據先復制到顯存里才能計算,計算完又需要把數據復制回來,而主要時間開銷都在數據的復制里面。
現實情況下,循環體里不可能只有一行代碼,假設循環體里有10個語句,那么CPU的執行時間就會翻10倍,而GPU的執行時間也會翻10倍,但是由于主要耗時操作是數據的復制,所以實際增長不會特別明顯。
下面我們修改一下代碼。
extern "C" _declspec(dllexport) void Sum(int* s, int a[], int b[],int length) {array_view<const int, 1> aArray(length, a);array_view<const int, 1> bArray(length, b);array_view<int, 1> sum(length, s);parallel_for_each(sum.extent,[=](index<1> idx) restrict(amp) {sum[idx] = aArray[idx] + bArray[idx];if (idx[0] % 5 == 0) {sum[idx] += 5;}if (idx[0] % 7 == 0) {sum[idx] += 7;}if (idx[0] % 11 == 0) {sum[idx] += 11;}if (idx[0] % 13 == 0) {sum[idx] += 13;}if (idx[0] % 17 == 0) {sum[idx] += 17;}}); } watch1.Start(); for(int i = 0; i < size; i++) {s[i] = a[i] + b[i];if (i % 5 == 0){s[i] += 5;}if (i % 7 == 0){s[i] += 7;}if (i % 11 == 0){s[i] += 11;}if (i % 13 == 0){s[i] += 13;}if (i % 17 == 0){s[i] += 17;} } watch1.Stop(); Console.WriteLine("CPU耗時:" + watch1.Elapsed.TotalMilliseconds);這次改用100萬量級的數據
?現在GPU的優勢就完全體現出來了
總結
以上是生活随笔為你收集整理的C#的GPU加速方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黑苹果安装后不能启动Windows解决
- 下一篇: C#判断线段是否相交