今天先来写一个用GPU输出的“helloword”
写一个CUDA程序,需要这么几个步骤:
用专用的扩展名.cu来创建一个源文件
使用CUDAnvcc编译器来编译程序
命令行运行可执行文件,这个文件有可以再GPU上运行的内核代码
//#include<iostream>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
//using namespace std;
//内核函数,这个global修饰符的作用就是,告诉编译器,这个函数从cpu中调用,然后再GPU上执行
__global__ void helloFromGPU() {
printf("HELLO WORD\n");
}
int main() {
printf("hello word from GPU" );
//<<<>>>三重尖括号表示的是主线程到设备端代码的调用,一个内核很熟通过一组线程来执行,所有的线程执行相同代码,如线面的,就是调用了10个GPU线程
helloFromGPU <<<1, 10 >>>();
cudaDeviceReset();
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/77ecc97bf7a3c92aacd7d37113890450.png)
图一:helloword!
代码调了半天,最后发现是如果加上了iostream这个库,他就产生链接问题,就报错,只要你用stdio.h
这个库,然后printf,他就没错,查了一会,也没找到啥原因,emm慢慢到后面再说吧。
当你吧线程数改成20的时候,就调动了20个gpu线程执行,那就输出了20个。
![](https://i-blog.csdnimg.cn/blog_migrate/be79175a34141e1228072517805b6721.png)
图二:运行二十个线程
一个典型的CUDA编程结构包含五个主要步骤:
分配GPU内存空间
从CPU内存中拷贝数据到GPU内存
调用CUDA核函数来完成指定运算
将数据从CPU拷回CPU内存
释放CPU内存
上面的输出helloword仅仅只用了前三部。
二、简单理解CUDA编程模型:
CUDA编程模型提供了一个计算机架构抽象作为应用程序和其可用硬件之间的桥梁,除了和其他并行编程模型共有的抽象以外,CUDA编程模型还利用GPU架构的计算能力,提供了两个特有的功能:
一种通过层次结构在GPU中组织线程的方法
一种通过层次结构在GPU中访问内存呢的方法
内核(Kernel)是CUDA编程模型的一个重要组成,其代码在GPU上运行。多数情况下,主机可以独立的对设备进行操作,内核一旦被启动,管理权立即返回给主机,释放CPU来执行由设备上运行的并行代码实现的额外的任务,由于CUDA编程模型主要是异步的,因此,在GPU山进行的运算可以和主机-设备通信重叠。一个典型的CUDA程序实现流程要遵循以下的模式:
把数据从CPU拷贝到GPU
调用核函数对存储在GPU内存中的数据进行操作
将数据从GPU内存传回到CPU内存
三、内存管理
CUDA假设西永是由一个主机和一个设备组成的,而且各自拥有独立的内存,核函数是在设备上运行的,CUDA在运行时负责分配和释放内存,并且在主机内存和设备内存之间传输数据;
用于执行CPU内存分配的是cuadMalloc函数:
cudaError_t cuadMalloc(void** devptr,size_t size)
//负责向设备分配一定字节的线性内存,并且以devprt的形式返回指向所分配内存的指针,
标准的c函数 | CUDA C函数 | 标准的C函数 | CUDA C函数 |
malloc | cudaMalloc | memset | cudaMemest |
memcpy | cudaMemcpy | free | cudaFree |
cudaMalloc和标准的C语言一摸一样,就是人家是在GPU上分配内存空间,cudaMemcpy负责主机和设备之间的数据传输:
cudaError_t cudaMemcpy(void *dst,void *src,size_t count,cudaMemcpyKind kind)
//从src指向源存储区复制一定数量的字节到dst指向的目标存储区,复制方法有kind决定
kind的类型
cudaMemcpyHostToHost |
cudaMemcpyHostToDevice |
cudaMemcpyDeviceToHost |
cudaMemcpyDeviceToDevice |
这host和device已经说的很清楚了,就不用再西江杀了,这个函数以同步的方式来执行,因为再cudaMemcpy函数返回以及传输完成之前主机的应用程序都是阻塞的,除了内核启动之外的CUDA调用都会返回一个错误的枚举类型cudaError_t如果GPU内存分配成功,函数会返回:
cudaSuccess,否则的话,返回cudaErrorMemoryAllocation,如果说,我们想知道运行时候的错误,很简单,就是用:
char* cudaGetErrorString(cudaError_t error)
这个函数来把错误的代码转换成可读的错误消息。
CUDA编程模型从GPU架构冲抽象出了一个内存层次结构,包括了两个部分:1、全局内存2、共享内存caution!!!这两个内存是不一样的,不要一看到全局就以为是共享的,实则不然,全局类似域CPU的系统内存,共享类似于CPU的缓存,而且,CUDA的共享内存是可以有CUDA C的内核直接控制的。
![](https://i-blog.csdnimg.cn/blog_migrate/219f72e852753b3ef2480b11f1438a09.png)
图三:CUDA内存结构
来一个主机端两个数组相加:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include<stdlib.h>
#include<string>
#include<time.h>
void sumArrayOnHost(float* a, float* b, float* c, const int n) {
for (int i = 0; i < n; i++)
{
c[i] = a[i] + b[i];
}
}
void initializeData(float* ip, int size) {
//generate different seed for random number
time_t t;
srand((unsigned int)time(&t));
for (int i = 0; i < size; i++) {
ip[i] = float(rand() & 0xFF) / 10.0f;
}
}
int main(int argc, char** argv) {
int nElem = 1024;
size_t nBytes = nElem * sizeof(float);
float* h_a, * h_b, * h_c;
h_a = (float*)malloc(nBytes);
h_b = (float*)malloc(nBytes);
h_c = (float*)malloc(nBytes);
initializeData(h_a, nElem);
initializeData(h_b, nElem);
printf("%d", nElem);
sumArrayOnHost(h_a, h_b, h_c, nElem);
for (int i = 0; i < nElem; i++) {
printf("%d", h_c[i]);
printf("\n");
}
free(h_a);
free(h_b);
free(h_c);
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/55b55bada9d6741dc40d7840578a9880.png)
图四:主机端运行结果(只是截取了一部分)
ok,运行没有问题,现在,我们把他给改到GPU端:
//使用CUDAmalloc再Gpu上申请内存
cudaMalloc((float**)&d_a, nBytes);
cudaMalloc((float**)&d_b, nBytes);
cudaMalloc((float**)&d_c, nBytes);
//用cudaMemcpy函数把数据从主机内存拷贝到GPU全局内存中,使用cudaMemcpyHostToDevice指定拷贝方向
//数据被转移到GPU全局内存之后,主机端调用核函数在Gpu上进行数组求和,一旦内核被调用,控制权立即被传回主机,这样的话
//和函数在GPU上运行的时候,主机可以运行其他函数,因此,内核和主机是异步的
//内核在GPU上完成处理之后,结果会以数组d_c的形式存储在GPU的全局内存中,然后用cudaMemcpy函数把结果从GPU复制回到主机数组gpuref中,
cudaMemcpy(d_a, h_a, nBytes, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, nBytes, cudaMemcpyHostToDevice);:
cudaMemcpy(h_c, d_c, nBytes, cudaMemcpyDeviceToHost);
//然后就是释放GPU的内存空间:
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
但是,书上没有讲解到底该怎么样设置核函数,我按找书上的讲的,加了这些,但,运行一会就出现了访问冲突,这里可以不必细究,慢慢的学着学着,就会了,ok,今天就到这里吧。