zoukankan      html  css  js  c++  java
  • C#使用CUDA

    随着信息处理的爆炸增长,传统使用CPU计算已经无法满足计算作业增长的需求,GPU的出现为批量作业提供了新的契机。GPU计算拥有很类库,比如CUDA、OpenCL等,但是可以发现CUDA是其中相对比较成熟的一个,也是目前应用最为广发的一种。于此同时开发语言的飞速发展,呈现百花齐放状态,C#语言简单高效,所以本文讲述如何使用C#调用CUDA进行GPU计算。

    前言

    最近几年计算机有了巨大发展,各种开发语言百花齐放,但是笔者还是钟爱C#语言,尤其是目前微软的生态开放,.NET 技术的跨平台加上方便高效的Visual Studio IDE,使得C#语言的占比不断增加。

    【B站小飞机】C#调用CUDA实现GPU计算

    本文使用的是C#语言调用C++开发的CUDA类库实现GPU计算,基本条件如下:

    选项 内容
    操作系统 Windows 10
    CUDA版本 10.1
    Visual Studio 2019 Enterprise

    上述的环境是笔者的环境,如有异同按实际条件。

    方法论

    本小结介绍使用C++语言开发CUDA程序的共享链接库的方法以及如何使用C#语言调用CUDA类库。

    创建C++类库

    首先打开Visual Studio,然后创建C++的动态链接库项目,注意一定是动态链接库,请勿使用静态链接库,可能会有错误。

    20190729092151

    20190729092227

    项目创建完毕之后可以看到下面的界面:

    20190729092250

    创建一个和外界交互的cudaApi.h文件、两个和CUDA相关的文件CudaKernel.cuhCudaKernel.cu,当然可以缺省为一个文件,但是本着约定大于配置的原则,建议使用两个文件,一个是CUDA的源码文件一个是CUDA的头文件,在头文件中声明函数,源码文件对函数进行实现,之后效果如下:

    20190729092322

    20190729092433

    然后配置一下工程,需要注意的是建议全局是x64环境,必须保证C++的动态链接库项目和C#的项目是同一种类型,一般FX(.NET Framework)默认的是Any CPU,不要使用这个选项,直接指定x64或者x86。

    右键选中项目,【生成依赖】 --> 【自定义生成】 --> 【勾选CUDA10】(当然不排除你的事CUDA9或者CUDA8)

    20190729094355

    20190729094420

    然后选择两个CUDA的核文件,选择属性,选中CUDA C/C++

    20190729094624

    20190729094656

    最后选中项目右键,找到【连接器】--> 【输入】,在类库中输入cudart.lib,点击确定。

    20190729095129

    作为初期实验,建议先复制我的代码,先理解这个流程之后再自己自定义创建CUDA类库。

    //cudaApi.h
    #pragma once
    #define CUDADD_API __declspec(dllexport)
    extern "C" {
    	//CUDA API
    	CUDADD_API int arrayAdd(int* a, int* b, int* c, int size);
    }
    
    //CudaKernel.cuh
    #include "cudaApi.h"
    #include "cuda_runtime.h"  
    #include "device_launch_parameters.h"
    
    __global__ void addKernel(int* c, const int* a, const int* b);
    CUDADD_API int arrayAdd(int* a, int* b, int* c, int size);
    

    CUDA核心实现代码(^{[1]})

    //CudaKernel.cu
    #include "CudaKernel.cuh"
    
    __global__ void addKernel(int* c, const int* a, const int* b)
    {
    	int i = threadIdx.x;
    	c[i] = a[i] + b[i];
    }
    CUDADD_API int arrayAdd(int c[], int a[], int b[], int size)
    {
    	int result = -1;
    	int* dev_a = 0;
    	int* dev_b = 0;
    	int* dev_c = 0;
    	cudaError_t cudaStatus;
    
    	cudaStatus = cudaSetDevice(0);
    	if (cudaStatus != cudaSuccess) {
    		result = 1;
    		goto Error;
    	}
    	cudaStatus = cudaMalloc((void**)& dev_c, size * sizeof(int));
    	if (cudaStatus != cudaSuccess) {
    		result = 2;
    		goto Error;
    	}
    	cudaStatus = cudaMalloc((void**)& dev_a, size * sizeof(int));
    	if (cudaStatus != cudaSuccess) {
    		result = 3;
    		goto Error;
    	}
    	cudaStatus = cudaMalloc((void**)& dev_b, size * sizeof(int));
    	if (cudaStatus != cudaSuccess) {
    		result = 4;
    		goto Error;
    	}
    	cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
    	if (cudaStatus != cudaSuccess) {
    		result = 5;
    		goto Error;
    	}
    	cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);
    	if (cudaStatus != cudaSuccess) {
    		result = 6;
    		goto Error;
    	}
    	addKernel << <10, size >> > (dev_c, dev_a, dev_b);
    	cudaStatus = cudaDeviceSynchronize();
    	if (cudaStatus != cudaSuccess) {
    		result = 7;
    		goto Error;
    	}
    	cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);
    	if (cudaStatus != cudaSuccess) {
    		result = 8;
    		goto Error;
    	}
    	result = 0;
    	cudaStatus = cudaDeviceReset();
    	if (cudaStatus != cudaSuccess) {
    		return 9;
    	}
    Error:
    	cudaFree(dev_c);
    	cudaFree(dev_a);
    	cudaFree(dev_b);
    	return result;
    }
    

    重新生成程序如果不报错就可以成功生成一个CUDA的DLL动态链接库。

    20190729095458

    20190729095619

    到此为止CUDA的动态链接库编写完成,开始编写C#程序,本程序使用的是.NET Core 2.2,如果你的电脑没有此环境可以自己创建一个C#的控制台项目,代码均相同。

    创建C#类库

    创建C#项目不再赘述,直接创建控制台项目即可完成测试,下面提供一个我的C#的一个类作为参考:

    //CudaRunner.cs
    class CudaRunner
        {
            public void Run()
            {
                var a = new int[] { 1, 2, 3, 45, 456, 454, 1, 4, 65, 32, 456, 1, 56, 32, 512, 3, 5416, 86, 54, 4236, 12, 113, 321 };
                var b = new int[] { 1, 2, 3, 45, 456, 454, 1, 4, 65, 32, 456, 1, 56, 32, 512, 3, 5416, 86, 54, 4236, 12, 113, 321 };
                var c = new int[a.Length];
    
                arrayAdd(c, a, b, a.Length);
    
                for (int i = 0; i < c.Length; i++)
                {
                    Console.WriteLine("{0} + {1} = {2}", a[i], b[i], c[i]);
                }
            }
            //.NET Framework 可以相对路径,.NET Core 就需要用绝对路径,或者自己配置
            [DllImport(@"C:Usersmuxuansource
    eposCudaSharpDemox64DebugCudaSharp.dll", CallingConvention = CallingConvention.StdCall)]
            public static extern int arrayAdd(int[] c, int[] a, int[] b, int size);
        }
    

    然后在控制台的Program.cs中调用一下即可,再次强调,这里不要使用Any CPU!!!

    20190729100456

    实验环节

    实验环节很简单,如果你是用的我的程序可以直接运行查看效果,这是一个矩阵的加法。

    20190729100704

    结论与总结

    到上面的实验环节基本程序部分已经结束,但是你可能会发现使用GPU计算这些数据的速度并没有CPU的快速,原因在哪呢?归结一下两个原因(^{[2]})

    1. 计算量太小,不够复杂。GPU的优势是核心较多,每一个核心的计算时间越长,整体的时间差异就会越明显,比如每一个核心计算数耗时是1ms,有1000个计算大概需要耗时1s左右,但是对于GPU(1060)超过1000个CUDA核心时,将在1ms内计算完毕,CPU的理论时间消耗是GPU的1000倍左右。

    2. GPU设备初始化。初始化时间主要来自几个部分:设备初始化、数据拷贝,其中数据初始化耗时是不可控的,属于硬件底层,数据拷贝是指从CPU拷贝数据到GPU计算数据,然后GPU将数据重新拷贝到CPU的过程,这个过程耗时可控也是主导部分。

    参考文献

    [1] C与CUDA混合编程的配置问题

    [2] 新手问下关于CUDA效率的问题

    附录

    源码:蓝奏云CSDN

  • 相关阅读:
    构造函数作为友元函数的参数
    引用调用
    分块查找
    折半查找
    c++中map按key和value排序
    STL之map学习实例
    STL之stack
    STL之map
    STL之string
    STL之template类模板
  • 原文地址:https://www.cnblogs.com/muxuan/p/11891771.html
Copyright © 2011-2022 走看看