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. MVC学习-发送请求

    在HomeControl中添加一个Action,代码如下: public ActionResult Add() { return View(); } 当View()中不写任何参数时,默认会调用同名的视 ...

  2. [ Luogu 4917 ] 天守阁的地板

    \(\\\) \(Description\) 定义二元函数\(F(x,y)\)表示,用 \(x\times y\) 的矩形不可旋转的铺成一个任意边长的正方形,所需要的最少的矩形个数. 现在\(T\)组 ...

  3. Spring+Spring MVC+Hibernate环境搭配

    Spring+Spring MVC+Hibernate简称"SSH".Spring容器是Spring的核心,该 容器负责管理spring中的java组件.Spring的核心机制:依 ...

  4. Spring scheduled cron 表达式

    一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素. 按顺序依次为 秒(0~59) 分钟(0~59) 小时(0~23) 天(月)(0~31,但是你需要考虑你月的天数) 月(0~11) 天( ...

  5. PHP几个常用的概率算法

    算法一 /** * 全概率计算 * * @param array $p array('a'=>0.5,'b'=>0.2,'c'=>0.4) * @return string 返回上面 ...

  6. 如何让一个div里面的div垂直居中?

    如何让一个div里面的div垂直居中? 如何让上面灰色有文字那个div和背景图标垂直居中,不管屏幕大小有好大,始终在垂直方向上的中间.上面有整个布局和样式表,谢谢高手指点 CSS3时代当然要用CSS3 ...

  7. 安卓app测试之Monkey测试

    一.Monkey特点 1.运行时机:一般是产品稳定后 首轮功能测试完成的夜间进行 2.需要知道packageName 3.目的:主要测试产品是否存在崩溃问题和ANR问题. 二.获取包名的两个方法 首先 ...

  8. 机器学习_K近邻Python代码详解

    k近邻优点:精度高.对异常值不敏感.无数据输入假定:k近邻缺点:计算复杂度高.空间复杂度高 import numpy as npimport operatorfrom os import listdi ...

  9. C++写注册表

    1.       注册表简介 注册表是为Windows NT和Windows95中所有32位硬件/驱动和32位应用程序设计的数据文件,用于存储系统和应用程序的设置信息.16位驱动在Winnt (Win ...

  10. 易忘小技巧--yum

    写在前面:日常维护系统中,如果不是天天接触,难免会忘记一些实用小技巧,本文记录自己使用的小技巧,有新发现时,会长期更新 安装epel和dnf源 # 安装epel yum -y install epel ...