抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

本文将通过C#调用dll的方法来实现并发计算

Dll定义

在VS2019里新建动态链接库项目,在pch.h里定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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里实现该函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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:数据源

如果是二维数组,则要改成下面的形式

1
array_view<const int, 2> aArray(width,height, a);

parallel_for_each语句能够进行并发计算,index<1>指idx是一维的,如果是二维数组,需要改成index<2>,此时idx相当于(i,j),通过idx[0]和idx[1]获得行号和列号

例如

1
2
int row = idx[0];
int col = idx[1];

aArray[idx]和aArray(row,col)是等效的

Dll导入

将上述代码生成dll,并放在C#程序的目录下

导入刚刚写的dll

1
2
[DllImport("Dll1.dll", EntryPoint = "Sum", CallingConvention = CallingConvention.Cdecl)]
public static extern void Sum(IntPtr s,int[] a, int[] b,int length);

随机数求和

生成随机数数组,求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
}

DearXuan
使用StopWatch类来计算耗时(命名空间System.Diagnostics)

1
2
3
4
5
6
7
8
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);
1
2
3
4
5
Stopwatch watch2 = new Stopwatch();
watch2.Start();
Sum(p, a, b, size);
watch2.Stop();
Console.WriteLine("GPU耗时:" + watch2.Elapsed.TotalMilliseconds);

由于加载dll本身需要时间,所以在计时之前需要先调用一次Sum函数.
DearXuan
测试代码是计算4亿个数的和,可以看到GPU计算比CPU计算少了300毫秒,但是CPU在循环2亿次的情况下居然仅仅比GPU多了300毫秒,这是因为GPU无法从内存读取数据,需要把数据先复制到显存里才能计算,计算完又需要把数据复制回来,而主要时间开销都在数据的复制里面.

现实情况下,循环体里不可能只有一行代码,假设循环体里有10个语句,那么CPU的执行时间就会翻10倍,而GPU的执行时间也会翻10倍,但是由于主要耗时操作是数据的复制,所以实际增长不会特别明显.

下面我们修改一下代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
}
}
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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万量级的数据

DearXuan

现在GPU的优势就完全体现出来了

评论