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. SQL数据库,增加查询修改以及防sql写入攻击

    SQL添加信息 SQL查询信息 SQL修改信息 SQL语句写入攻击: 普通语句添加信息 sql写入语句攻击: 解决方法:分开传送语句与参数关键:@

  2. 【JAVA 学习笔记2】if使用例子

    int a =3; if (a%2==0) { System.out.println(a+" 是偶数"); System.out.println(a+" 不是奇数&quo ...

  3. Angular——自定义过滤器

    基本介绍 除了使用AngularJS内建过滤器外,还可以根业务需要自定义过滤器,通过模块对象实例提供的filter方法自定义过滤器. 基本使用 (1)input是将绑定的数据以参数的形式传入 (2)i ...

  4. java web 学习笔记 - jsp用的文件上传组件 SmartUpload

    ---恢复内容开始--- 1. SmartUpload 此控件在jsp中被广泛的使用,而FileUpload控件主要是用在框架中 2. 如果想要使用,需要在tomcat的lib目录中,将SmartUp ...

  5. How a stack frame works 栈帧的要素与构建步骤

    http://en.citizendium.org/wiki/Stack_frame To use a stack frame, a thread keeps two pointers, often ...

  6. 【Hadoop】二、HDFS文件读写流程

    (二)HDFS数据流   作为一个文件系统,文件的读和写是最基本的需求,这一部分我们来了解客户端是如何与HDFS进行交互的,也就是客户端与HDFS,以及构成HDFS的两类节点(namenode和dat ...

  7. idea安装及使用

    使用:https://blog.csdn.net/qq_42303709/article/details/81983208 安装教程:https://blog.csdn.net/yl171272518 ...

  8. mybatis中resultMap引发的吐血bug

    简单的讲: 问题背景:如果在写mybatis中的resultMap时,不下心将resultMapde id写成映射接口的名字,会发生什么? 结论:单元测试进度条卡住但不报错, Tomcat运行不报错, ...

  9. <SpringMvc>入门六 异常处理

    如果不做异常处理,那么一直将错误向上抛出,则会最后在页面上显示错误代码 服务启动后,访问test1方法,页面会报500 为了提示友好的错误页面,所以需要做异常处理 1.编写自定义异常类(做提示信息的) ...

  10. Rsync远程同步工具使用

    rsync远程同步工具使用 Rsync(remote synchronize) 是一个远程数据同步工具,可以使用"Rsync算法"同步本地和远程主机之间的文件.Rsync的好处是只 ...