CUDA编程(二)

CUDA初始化与核函数

CUDA初始化

在上一次中已经说过了,CUDA成功安装之后,新建一个project还是十分简单的,直接在新建项目的时候选择NVIDIA CUDA项目就能够了,我们先新建一个MyCudaTest project。删掉自带的演示样例kernel.cu。然后新建项,新建一个CUDA C/C++ File ,我们首先看一下怎样初始化CUDA,因此我命名为InitCuda.cu

首先我们要使用CUDA的RunTime API 所以 我们须要include cuda_runtime.h

#include <stdio.h> 

//CUDA RunTime API
#include <cuda_runtime.h>

接下来这个函数会调用 runtime API 中 有关初始化CUDA的内容

//CUDA 初始化
bool InitCUDA()
{
int count; //取得支持Cuda的装置的数目
cudaGetDeviceCount(&count); //没有符合的硬件
if (count == 0) {
fprintf(stderr, "There is no device.\n");
return false;
} int i; for (i = 0; i < count; i++) {
cudaDeviceProp prop;
if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) {
if (prop.major >= 1) {
break;
}
}
} if (i == count) {
fprintf(stderr, "There is no device supporting CUDA 1.x.\n");
return false;
} cudaSetDevice(i); return true;
}

这段程序首先会调用cudaGetDeviceCount 函数。获得支持 CUDA 的GPU的数量,假设计算机上没有支持 CUDA 的装置,则会传回 1,而这个1是device 0 ,device0 仅仅是一个仿真装置,可是CUDA的非常多功能都不支持(不支持CUDA1.0以上版本号),因此我们要真正确定系统上是否有支持CUDA的装置,须要对每一个device调用cudaGetDeviceProperties,来获得它们的详细參数,以及所支持的CUDA版本号(prop.major 和 prop.minor 分别代表装置支持的版本号号码,比如 6.5 则 prop.major 为 6 而prop.minor 为 5)

cudaGetDeviceProperties除了能够获得装置支持的 CUDA 版本号之外,还有装置的名称、内存的大小、最大的 thread 数目、运行单元的频率等等。详情可參考NVIDIA 的 CUDA Programming Guide。

在找到支持 CUDA 1.0 以上的装置之后。就能够呼叫 cudaSetDevice 函式,把它设为眼下要使用的显卡。

以下我们在Main函数中调用InitCUDA函数,由于我们使用VS,所以直接ctrl+F5编译运行就能够了。运行时假设系统上有支持 CUDA 的装置。应该会显示 CUDA initialized。

int main()
{ if (!InitCUDA())
{
return 0;
} printf("CUDA initialized.\n"); return 0; }

完整程序:

#include <stdio.h> 

//CUDA RunTime API
#include <cuda_runtime.h> //CUDA 初始化
bool InitCUDA()
{
int count; //取得支持Cuda的装置的数目
cudaGetDeviceCount(&count); //没有符合的硬件
if (count == 0) {
fprintf(stderr, "There is no device.\n");
return false;
} int i; for (i = 0; i < count; i++) {
cudaDeviceProp prop;
if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) {
if (prop.major >= 1) {
break;
}
}
} if (i == count) {
fprintf(stderr, "There is no device supporting CUDA 1.x.\n");
return false;
} cudaSetDevice(i); return true;
} int main()
{ if (!InitCUDA())
{
return 0;
} printf("CUDA initialized.\n"); return 0; }

CUDA核函数

完毕了CUDA的初始化检查操作,以下我们就能够使用CUDA完毕一些简单计算了。这里我们打算计算一系列数字的立方和。

所以我们先写了一个随机函数:

#define DATA_SIZE 1048576

int data[DATA_SIZE];

//产生大量0-9之间的随机数
void GenerateNumbers(int *number, int size)
{
for (int i = 0; i < size; i++) {
number[i] = rand() % 10;
}
} //生成随机数(main中调用)
//GenerateNumbers(data, DATA_SIZE);

该函数会产生一大堆 0 ~ 9 之间的随机数,然后我们要对他们进行立方和操作。

那么我们怎样让这个工作在显卡上完毕呢?首先第一件事非常显而易见,这些数字不能放在内存里了,而是要拷贝到GPU的显存上。以下我们就来看一下数据复制的部分。

Host&Device架构:

上一次已经讲过关于CUDA架构的一些基础了。这里再略微复习一下。在 CUDA 的架构下,一个程序分为两个部份:host 端和 device 端。Host 端是指在 CPU 上运行的部份,而 device 端则是在显示芯片上运行的部份。Device 端的程序又称为 “kernel”。通常 host 端程序会将数据准备好后,拷贝到显卡的内存中,再由显示芯片运行 device 端程序。完毕后再由 host 端程序将结果从显卡的内存中取回。

我们须要把产生的数据拷贝到Device端的RAM,才干在显卡上完毕计算。因此我们首先开辟一块合适的显存。然后把随机数从内存复制进去。

    //生成随机数
GenerateNumbers(data, DATA_SIZE); /*把数据拷贝到显卡内存中*/ int* gpudata, *result; //cudaMalloc 取得一块显卡内存 ( 当中result用来存储计算结果 )
cudaMalloc((void**)&gpudata, sizeof(int)* DATA_SIZE);
cudaMalloc((void**)&result, sizeof(int)); //cudaMemcpy 将产生的随机数拷贝到显卡内存中
//cudaMemcpyHostToDevice - 从内存拷贝到显卡内存
//cudaMemcpyDeviceToHost - 从显卡内存拷贝到内存
cudaMemcpy(gpudata, data, sizeof(int)* DATA_SIZE,cudaMemcpyHostToDevice);

凝视已经写得比較明确了。cudaMalloc 和 cudaMemcpy 的使用方法和一般的 malloc 及 memcpy 相似,只是 cudaMemcpy 则多出一个參数,指示复制内存的方向。

在这里由于是从主内存拷贝到显卡内存。所以使用 cudaMemcpyHostToDevice。假设是从显卡内存到主内存,则使用cudaMemcpyDeviceToHost。

完毕了从内存到显存的数据拷贝之后,我们接下来就要在显卡上完毕计算了,怎样让程序跑在显卡上?答案是核函数。

CUDA核函数:

要写在显示芯片上运行的程序。在 CUDA 中,在函数前面加上__global__ 表示这个函式是要在显示芯片上运行的,所以我们仅仅要在正常函数之前加上一个__global__即可了:

// __global__ 函数 (GPU上运行) 计算立方和
__global__ static void sumOfSquares(int *num, int* result)
{
int sum = 0; int i; for (i = 0; i < DATA_SIZE; i++) { sum += num[i] * num[i] * num[i]; } *result = sum; }

在显示芯片上运行的程序有一些限制,首先最明显的一个限制——不能有传回值。另一些其它的限制,后面会慢慢提到。

运行核函数:

写好核函数之后须要让CUDA运行这个函数。

在 CUDA 中,要运行一个核函数,使用以下的语法:

    函数名称<<<block 数目, thread 数目, shared memory 大小>>>(參数...);

这里我们先不去并行,仅仅是单纯地完毕GPU计算,所以我们让block = 1。thread = 1,share memory = 0

    sumOfSquares<<<1, 1, 0>>>(gpudata, result);

计算完了,千万别忘了还要把结果从显示芯片复制回主内存上。然后释放掉内存~

    int sum;

    //cudaMemcpy 将结果从显存中复制回内存
cudaMemcpy(&sum, result, sizeof(int), cudaMemcpyDeviceToHost); //Free
cudaFree(gpudata);
cudaFree(result);

最后我们把结果打印出来就大功告成了:

    printf("GPUsum: %d \n", sum);

之后我们再用CPU计算一下来验证一下上面的过程是否有错,这一步还是十分必要的:

    sum = 0;

    for (int i = 0; i < DATA_SIZE; i++) {
sum += data[i] * data[i] * data[i];
} printf("CPUsum: %d \n", sum);

完整程序:

程序代码:

#include <stdio.h>
#include <stdlib.h> //CUDA RunTime API
#include <cuda_runtime.h> #define DATA_SIZE 1048576 int data[DATA_SIZE]; //产生大量0-9之间的随机数
void GenerateNumbers(int *number, int size)
{
for (int i = 0; i < size; i++) {
number[i] = rand() % 10;
}
} //CUDA 初始化
bool InitCUDA()
{
int count; //取得支持Cuda的装置的数目
cudaGetDeviceCount(&count); if (count == 0) {
fprintf(stderr, "There is no device.\n");
return false;
} int i; for (i = 0; i < count; i++) {
cudaDeviceProp prop;
if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) {
if (prop.major >= 1) {
break;
}
}
} if (i == count) {
fprintf(stderr, "There is no device supporting CUDA 1.x.\n");
return false;
} cudaSetDevice(i); return true;
} // __global__ 函数 (GPU上运行) 计算立方和
__global__ static void sumOfSquares(int *num, int* result)
{
int sum = 0; int i; for (i = 0; i < DATA_SIZE; i++) { sum += num[i] * num[i] * num[i]; } *result = sum; } int main()
{ //CUDA 初始化
if (!InitCUDA()) {
return 0;
} //生成随机数
GenerateNumbers(data, DATA_SIZE); /*把数据拷贝到显卡内存中*/ int* gpudata, *result; //cudaMalloc 取得一块显卡内存 ( 当中result用来存储计算结果 )
cudaMalloc((void**)&gpudata, sizeof(int)* DATA_SIZE);
cudaMalloc((void**)&result, sizeof(int)); //cudaMemcpy 将产生的随机数拷贝到显卡内存中
//cudaMemcpyHostToDevice - 从内存拷贝到显卡内存
//cudaMemcpyDeviceToHost - 从显卡内存拷贝到内存
cudaMemcpy(gpudata, data, sizeof(int)* DATA_SIZE, cudaMemcpyHostToDevice); // 在CUDA 中运行函数 语法:函数名称<<<block 数目, thread 数目, shared memory 大小>>>(參数...);
sumOfSquares << <1, 1, 0 >> >(gpudata, result); /*把结果从显示芯片复制回主内存*/ int sum; //cudaMemcpy 将结果从显存中复制回内存
cudaMemcpy(&sum, result, sizeof(int), cudaMemcpyDeviceToHost); //Free
cudaFree(gpudata);
cudaFree(result); printf("GPUsum: %d \n", sum); sum = 0; for (int i = 0; i < DATA_SIZE; i++) {
sum += data[i] * data[i] * data[i];
} printf("CPUsum: %d \n", sum); return 0;
}

运行结果:

总结:

这次给大家介绍了CUDA的初始化和怎样在显卡上运行程序。即先将数据从内存拷贝到显存。再写好运算的核函数,之后用CUDA调用核函数,完毕GPU上的计算。之后当然不要忘记将结果复制回内存,释放掉显存。

总的来说一个CUDA程序的骨架已经搭建起来了,而GPU计算的重中之重即并行加速还没有进行介绍,只是在加速之前我们另一件非常重要的事情须要考虑,那就是我们的程序究竟有没有加速,也就是我们要输出程序的运行时间,这个时间我们须要使用CUDA提供的一个Clock函数,能够取得GPU运行单元的频率,所以下一篇博客我将主要解说这个函数~希望能给大家的学习带来帮助~

參考资料:《深入浅出谈CUDA》

CUDA编程(二) CUDA初始化与核函数的更多相关文章

  1. CUDA编程-&gt;CUDA入门了解(一)

    安装好CUDA6.5+VS2012,操作系统为Win8.1版本号,首先下个GPU-Z检測了一下: 看出本显卡属于中低端配置.关键看两个: Shaders=384.也称作SM.或者说core/流处理器数 ...

  2. CUDA编程之快速入门

    CUDA(Compute Unified Device Architecture)的中文全称为计算统一设备架构.做图像视觉领域的同学多多少少都会接触到CUDA,毕竟要做性能速度优化,CUDA是个很重要 ...

  3. CUDA编程之快速入门【转】

    https://www.cnblogs.com/skyfsm/p/9673960.html CUDA(Compute Unified Device Architecture)的中文全称为计算统一设备架 ...

  4. cuda编程基础

    转自: http://blog.csdn.net/augusdi/article/details/12529247 CUDA编程模型 CUDA编程模型将CPU作为主机,GPU作为协处理器(co-pro ...

  5. CUDA刷新器:CUDA编程模型

    CUDA刷新器:CUDA编程模型 CUDA Refresher: The CUDA Programming Model CUDA,CUDA刷新器,并行编程 这是CUDA更新系列的第四篇文章,它的目标是 ...

  6. CUDA编程模型

    1. 典型的CUDA编程包括五个步骤: 分配GPU内存 从CPU内存中拷贝数据到GPU内存中 调用CUDA内核函数来完成指定的任务 将数据从GPU内存中拷贝回CPU内存中 释放GPU内存 *2. 数据 ...

  7. CUDA编程学习笔记1

    CUDA编程模型是一个异构模型,需要CPU和GPU协同工作. host和device host和device是两个重要的概念 host指代CPU及其内存 device指代GPU及其内存 __globa ...

  8. 不同版本CUDA编程的问题

    1 无法装上CUDA的toolkit 卸载所有的NVIDIA相关的app,包括NVIDIA的显卡驱动,然后重装. 2之前的文件打不开,one or more projects in the solut ...

  9. CUDA编程

    目录: 1.什么是CUDA 2.为什么要用到CUDA 3.CUDA环境搭建 4.第一个CUDA程序 5. CUDA编程 5.1. 基本概念 5.2. 线程层次结构 5.3. 存储器层次结构 5.4. ...

随机推荐

  1. 21 调试我们的C#程序

    我们写的C#程序,很多的时候不是能一次就写对的.尤其是在实际的项目中,你经常要做的工作就是不断修改你写的程序,让它正常运行.程序有错误,主要是两种情况.一种是程序报错了,不能正常运行下去.另一种是程序 ...

  2. LN : leetcode 515 Find Largest Value in Each Tree Row

    lc 515 Find Largest Value in Each Tree Row 515 Find Largest Value in Each Tree Row You need to find ...

  3. 富士康的盈利秒杀99%的A股公司:3星|《三联生活周刊》2018年10期

    三联生活周刊·最美的数学:天才为何成群到来(2018年10期) 本期专题是数学和成都,我都跳过去没看.其他内容也还有点意思. 总体评价3星. 以下是本期一些内容的摘抄,#号后面是kindle电子版中的 ...

  4. 动软生成器添加Mysql注释

    1.解决没有mysql注释问题 替换原文件下载地址 2.更新Models模板 <#@ template language="c#" HostSpecific="Tr ...

  5. 通过acdbblockreference 获得块名

    AcDbBlockReference *pBlkRef = AcDbBlockReference::cast(ent.object());     AcDbObjectId pBlkTblRecId; ...

  6. 当From窗体中数据变化时,使用代码获取数据库中的数据然后加入combobox中并且从数据库中取得最后的结果

    private void FormLug_Load(object sender, EventArgs e) { FieldListLug.Clear();//字段清除 DI = double.Pars ...

  7. ThinkPHP---拓展之jQuery的ajax

    [前言] 用Sublime开发时,推荐下载一个jQuery插件,可以智能化创建基本函数格式,支持自动生成,可以提高开发效率 (1)jQuery里ajax方法有几个? 答:有4个,分别为post.get ...

  8. 牛客多校Round 1

    Solved:1 rank:249 E. Removal dp i,j表示前i个数删除了j个且选择了第i个的答案 类似字符串的dp 预处理一下nex i_k即i后面k第一次出现的位置  就好转移了 # ...

  9. Invalid character found in the request target.The valid characters are defined in RFC 7230 and RFC3986

    Tomcat在 7.0.73, 8.0.39, 8.5.7 版本后,添加了对于http头的验证. 具体来说,就是添加了些规则去限制HTTP头的规范性 参考这里 具体来说: org.apache.tom ...

  10. Bullet:ORACLE Using SQL Plan Management(一)

    SQL Plan Management如何工作? 当一个SQL硬解析时,基于成本的优化器CBO会生成多个执行计划,并从这些执行计划中选择一个优化器认为最低成本的执行计划. 如果SQL plan bas ...