GPU的并行运算能力远超CPU,有时候我们会需要用到超大数据并行运算,可以考虑用GPU实现,这是一篇C#调用GPU进行运算的入门教程.

1: 下载相关的库:

https://sourceforge.net/projects/openclnet/

看起来已经N久没更新了, 不过没关系,这只是API声明和参数,opencl本身是有在更新的.

里面有源码也有DLL,可以引用DLL,也可以直接把源码添加到工程使用.(建议直接添加代码...)

*** 需要注意的是 ***:自己建立的工程有个默认的Program类,要改成别的名字,不然会和这里面一个同名的类冲突....

2:建立工程

打开VS建立一个C#控制台工程,Program类改名为MainProgram,添加OpenCL.Net源码引用

项目属性里改为[允许不安全代码]:

3:在MainProgram里声明引用:

using OpenCLNet;
using CL = OpenCLNet;

4:在项目里添加一个Extend类,内容如下

public static class Extend
{
/// <summary>
/// 取指针
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static unsafe IntPtr ToIntPtr(this int[] obj)
{
IntPtr PtrA = IntPtr.Zero;
fixed (int* Ap = obj) return new IntPtr(Ap);
}
}//End Class

5:在MainProgram把一段运行在GPU的代码放在C#的字符串里:

#region OpenCL代码
private static string CLCode = @"
__kernel void vector_add_gpu(__global int* src_a, __global int* src_b, __global int* res)
{
const int idx = get_global_id(0);
res[idx] =src_a[idx] + src_b[idx];
} __kernel void vector_inc_gpu(__global int* src_a, __global int* res)
{
const int idx = get_global_id(0);
res[idx] =src_a[idx] + 1;
}
";
#endregion

6:选中一个设备

在大多数电脑上有1个CPU和2个GPU(集显,独显),有的电脑会有更多或者更少,这里需要选中一个

//获取平台数量
OpenCL.GetPlatformIDs(32, new IntPtr[32], out uint num_platforms);
var devs = new List<Device>();
//枚举所有平台下面的设备(CPU和GPU)
for (int i = 0; i < num_platforms; i++)
{
//这里后面有个参数,是Enum,这里选择GPU,表示只枚举GPU,在没有GPU的电脑上可以选CPU,也可以传ALL,会把所有设备枚举出来供选择
devs.AddRange(OpenCL.GetPlatform(i).QueryDevices(DeviceType.GPU));
} //选中运算设备,这里选第一个其它的释放掉
var oclDevice = devs[0];

7:配置上下文

上下文用来描述CPU与运算设备之间的主从关系.

//根据配置建立上下文
var oclContext = oclDevice.Platform.CreateContext(
new[] { (IntPtr)ContextProperties.PLATFORM, oclDevice.Platform.PlatformID, IntPtr.Zero, IntPtr.Zero },
new[] { oclDevice },
(errInfo, privateInfo, cb, userData) => { },
IntPtr.Zero
);

8:创建命令队列

opencl的命令要放到队列里,然后一次性调用执行方法等待执行完毕,它可以乱序执行,也可以顺序执行.如果你想等某命令执行完再继续,可以在中间加上栅栏(下面会讲)

//创建命令队列
var oclCQ = oclContext.CreateCommandQueue(oclDevice, CommandQueueProperties.PROFILING_ENABLE);

9:编译OpenCL代码,并引出两个Kernel

//定义一个字典用来存放所有核
var Kernels = new Dictionary<string, Kernel>();
#region 编译代码并导出核
var oclProgram = oclContext.CreateProgramWithSource(CLCode);
try
{
oclProgram.Build();
}
catch (OpenCLBuildException EEE)
{
Console.WriteLine(EEE.BuildLogs[0]);
Console.ReadKey(true);
throw EEE;
//return null;
}
foreach (var item in new[] { "vector_add_gpu", "vector_inc_gpu" })
{
Kernels.Add(item, oclProgram.CreateKernel(item));
}
oclProgram.Dispose();
#endregion

10:调用Kernel示例:

#region 调用vector_add_gpu核
{
var A = new int[] { 1, 2, 3, 1722 };
var B = new int[] { 456, 2, 1, 56 };
var C = new int[4];
//在显存创建缓冲区并把HOST的数据拷贝过去
var n1 = oclContext.CreateBuffer(MemFlags.READ_WRITE | MemFlags.COPY_HOST_PTR, A.Length * sizeof(int), A.ToIntPtr());
var n2 = oclContext.CreateBuffer(MemFlags.READ_WRITE | MemFlags.COPY_HOST_PTR, B.Length * sizeof(int), B.ToIntPtr());
//还有一个缓冲区用来接收回参
var n3 = oclContext.CreateBuffer(MemFlags.READ_WRITE, B.Length * sizeof(int), IntPtr.Zero);
//把参数填进Kernel里
Kernels["vector_add_gpu"].SetArg(0, n1);
Kernels["vector_add_gpu"].SetArg(1, n2);
Kernels["vector_add_gpu"].SetArg(2, n3);
//把调用请求添加到队列里,参数分别是:Kernel,数据的维度,每个维度的全局工作项ID偏移,每个维度工作项数量(我们这里有4个数据,所以设为4),每个维度的工作组长度(这里设为每4个一组)
oclCQ.EnqueueNDRangeKernel(Kernels["vector_add_gpu"], 1, new[] { 0 }, new[] { 4 }, new[] { 4 });
//设置栅栏强制要求上面的命令执行完才继续下面的命令.
oclCQ.EnqueueBarrier();
//添加一个读取数据命令到队列里,用来读取运算结果
oclCQ.EnqueueReadBuffer(n3, true, 0, C.Length * sizeof(int), C.ToIntPtr());
//开始执行
oclCQ.Finish(); n1.Dispose();
n2.Dispose();
n3.Dispose();
C = C;//在这里打断点,查看返回值
}
// */
#endregion

11:释放资源

//按顺序释放之前构造的对象
oclCQ.Dispose();
oclContext.Dispose();
oclDevice.Dispose();

MainProgram所有代码:

class MainProgram
{ #region OpenCL代码
private static string CLCode = @"
__kernel void vector_add_gpu(__global int* src_a, __global int* src_b, __global int* res)
{
const int idx = get_global_id(0);
res[idx] =src_a[idx] + src_b[idx];
} __kernel void vector_inc_gpu(__global int* src_a, __global int* res)
{
const int idx = get_global_id(0);
res[idx] =src_a[idx] + 1;
}
";
#endregion static void Main(string[] args)
{
//获取平台数量
OpenCL.GetPlatformIDs(32, new IntPtr[32], out uint num_platforms);
var devs = new List<Device>();
//枚举所有平台下面的设备(CPU和GPU)
for (int i = 0; i < num_platforms; i++)
{
//这里后面有个参数,是Enum,这里选择GPU,表示只枚举GPU,在没有GPU的电脑上可以选CPU,也可以传ALL,会把所有设备枚举出来供选择
devs.AddRange(OpenCL.GetPlatform(i).QueryDevices(DeviceType.GPU));
} //选中运算设备,这里选第一个其它的释放掉
var oclDevice = devs[0];
for (int i = 1; i < devs.Count; i++) devs[i].Dispose(); //根据配置建立上下文
var oclContext = oclDevice.Platform.CreateContext(
new[] { (IntPtr)ContextProperties.PLATFORM, oclDevice.Platform.PlatformID, IntPtr.Zero, IntPtr.Zero },
new[] { oclDevice },
(errInfo, privateInfo, cb, userData) => { },
IntPtr.Zero
); //创建命令队列
var oclCQ = oclContext.CreateCommandQueue(oclDevice, CommandQueueProperties.PROFILING_ENABLE); //定义一个字典用来存放所有核
var Kernels = new Dictionary<string, Kernel>();
#region 编译代码并导出核
var oclProgram = oclContext.CreateProgramWithSource(CLCode);
try
{
oclProgram.Build();
}
catch (OpenCLBuildException EEE)
{
Console.WriteLine(EEE.BuildLogs[0]);
Console.ReadKey(true);
throw EEE;
//return null;
}
foreach (var item in new[] { "vector_add_gpu", "vector_inc_gpu" })
{
Kernels.Add(item, oclProgram.CreateKernel(item));
}
oclProgram.Dispose();
#endregion #region 调用vector_add_gpu核
{
var A = new int[] { 1, 2, 3, 1722 };
var B = new int[] { 456, 2, 1, 56 };
var C = new int[4];
//在显存创建缓冲区并把HOST的数据拷贝过去
var n1 = oclContext.CreateBuffer(MemFlags.READ_WRITE | MemFlags.COPY_HOST_PTR, A.Length * sizeof(int), A.ToIntPtr());
var n2 = oclContext.CreateBuffer(MemFlags.READ_WRITE | MemFlags.COPY_HOST_PTR, B.Length * sizeof(int), B.ToIntPtr());
//还有一个缓冲区用来接收回参
var n3 = oclContext.CreateBuffer(MemFlags.READ_WRITE, B.Length * sizeof(int), IntPtr.Zero);
//把参数填进Kernel里
Kernels["vector_add_gpu"].SetArg(0, n1);
Kernels["vector_add_gpu"].SetArg(1, n2);
Kernels["vector_add_gpu"].SetArg(2, n3);
//把调用请求添加到队列里,参数分别是:Kernel,数据的维度,每个维度的全局工作项ID偏移,每个维度工作项数量(我们这里有4个数据,所以设为4),每个维度的工作组长度(这里设为每4个一组)
oclCQ.EnqueueNDRangeKernel(Kernels["vector_add_gpu"], 1, new[] { 0 }, new[] { 4 }, new[] { 4 });
//设置栅栏强制要求上面的命令执行完才继续下面的命令.
oclCQ.EnqueueBarrier();
//添加一个读取数据命令到队列里,用来读取运算结果
oclCQ.EnqueueReadBuffer(n3, true, 0, C.Length * sizeof(int), C.ToIntPtr());
//开始执行
oclCQ.Finish(); n1.Dispose();
n2.Dispose();
n3.Dispose();
C = C;//在这里打断点,查看返回值
}
// */
#endregion //按顺序释放之前构造的对象
oclCQ.Dispose();
oclContext.Dispose();
oclDevice.Dispose();
}
}//End Class

运行效果:



至此,操作完成~

我在文中留了一个Kernel,你可以尝试调用看看.

相关代码git:

https://gitee.com/ASMTeam/CSharpOpenCLDemo

C#通过OpenCL调用显卡GPU做高效并行运算的更多相关文章

  1. OpenCL 双调排序 GPU 版

    ▶ 参考书中的代码,写了 ● 代码,核函数文件包含三中算法 // kernel.cl __kernel void bitonicSort01(__global uint *data, const ui ...

  2. 【原创】Linux环境下的图形系统和AMD R600显卡编程(6)——AMD显卡GPU命令格式

    前面一篇blog里面描述了命令环缓冲区机制,在命令环机制下,驱动写入PM4(不知道为何会取这样一个名字)包格式的命令对显卡进行配置.这一篇blog将详细介绍命令包的格式. 当前定义了4中命令包,分别是 ...

  3. 从头学pytorch(十三):使用GPU做计算

    GPU计算 默认情况下,pytorch将数据保存在内存,而不是显存. 查看显卡信息 nvidia-smi 我的机器输出如下: Fri Jan 3 16:20:51 2020 +------------ ...

  4. 如何调用sklearn模块做交叉验证

    终于搞明白了如何用sklearn做交叉验证!!! 一般在建立完模型之后,我们要预测模型的好坏,为了试验的可靠性(排除一次测试的偶然性)我们要进行多次测试验证,这时就要用交叉验证. sklearn中的s ...

  5. Kubernetes调用vSphere vSAN做持久化存储

    参考 1.vSphere Storage for Kubernetes 2.IBM vSphere Cloud Provider 3.GitHub vSphere Volume examples 一. ...

  6. 显卡 GPU 关系

    https://zhidao.baidu.com/question/1238935513507031339.htmlGraphic Processing Unit,意思就是图形处理器啊,显卡的由GPU ...

  7. python-----查看显卡gpu信息

    需要安装pynvml库. 下载地址为:https://pypi.org/project/nvidia-ml-py/#history pip安装的命令为: pip install nvidia-ml-p ...

  8. [转]linux 下 使用 c / c++ 调用curl库 做通信开发

    example:   1. http://curl.haxx.se/libcurl/c/example.html  2. http://www.libcurl.org/book:  1. http:/ ...

  9. win10家庭中文版CUDA+CUDNN+显卡GPU使用tensorflow-gpu训练模型安装过程(精华帖汇总+重新修改多次复现)

    查看安装包 pip list 本帖提供操作过程,具体操作网上有好多了,不赘述.红色字体为后来复现出现的问题以及批注 题外话: (1)python 的环境尽量保持干净,尽量单一,否则容易把自己搞晕,不知 ...

随机推荐

  1. 201521123010 《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  2. 25个最基本的JavaScript面试问题及答案

    1.使用 typeof bar === "object" 来确定 bar 是否是对象的潜在陷阱是什么?如何避免这个陷阱? 尽管 typeof bar === "objec ...

  3. StringBuffer的替换和反转和截取功能

    A:StringBuffer的替换功能 * public StringBuffer replace(int start,int end,String str): * 从start开始到end用str替 ...

  4. Python 接口测试(四)

    五:python数列化和反序列化 把python的对象编码转换为json格式的字符串,反序列化可以理解为:把json格式 字符串解码为python数据对象.在python的标准库中,专门提供了json ...

  5. 【个人笔记】《知了堂》前端mySql基础

    指定列之后添加: ALTER TABLE 表名 ADD 添加的新列名 INT AFTER 指定列之后 第一个位置: ALTER TABLE 表名 ADD 添加的新列名 varchar(20) AFTE ...

  6. Jenkins定时任务

    Jenkins配置定时任务 选中Job名称--配置—构建触发器—勾选“Build periodically” 如图中配置所示:该任务每天上午7点定时执行一次. 官方说明翻译 MINUTE HOUR D ...

  7. Spring 学习——基于Spring WebSocket 和STOMP实现简单的聊天功能

    本篇主要讲解如何使用Spring websocket 和STOMP搭建一个简单的聊天功能项目,里面使用到的技术,如websocket和STOMP等会简单介绍,不会太深,如果对相关介绍不是很了解的,请自 ...

  8. 在分布式数据库中CAP原理CAP+BASE

    本篇博文的内容均来源于网络,本人只是整理,仅供学习! 一.关系型数据库 关系型数据库遵循ACID规则 事务在英文中是transaction,和现实世界中的交易很类似,它有如下四个特性: 1.A (At ...

  9. 用css绘制各种图形

    1.用css绘制三角形 http://www.cnblogs.com/blosaa/p/3823695.html

  10. Centos7环境下使用Nginx托管.Net Core应用程序

    一.安装.Net Core 参考官方文档:https://www.microsoft.com/net/core#linuxcentos 1.添加dotnet产品Feed 在安装.NET Core之前, ...