C++编程笔记(GPU并行编程)
一、配置并使用
环境:Windows10 + CLion + VS2019
cuda的安装,并行的话只需要安装cuda,cuDNN就不必了
编译器设置,windows下建议使用MSVC,因为是官方支持的,记得架构一定要设置amd64
GPU版本架构查询网址
CmakeList.txt
编写
cmake_minimum_required(VERSION 3.22)#跟据自己的cmake版本来设置
project(CUDA_TEST2 LANGUAGES CXX CUDA)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CUDA_ARCHITECTURES 61)
#设置device函数的声名和定义分开,为了性能,不建议这样
set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
add_executable(CUDA_TEST2 main.cu)
#clion加的,好像可以不要
#set_target_properties(CUDA_TEST2 PROPERTIES
# CUDA_SEPARABLE_COMPILATION ON)
至此,就可以开始写代码了
二、代码
__global__
:定义在GPU上的核函数,GPU执行,从CPU调用,可以有参数,不能有返回值,global就是GPU的main函数,device就是其他函数__device__
:device修饰的函数定义在GPU上,device是设备函数,GPU执行,只能由GPU调用,调用规则与普通函数一样__host__
:host修饰的函数定义在CPU上,只能在CPU上调用,调用规则与普通函数一样- 同时加上host和device修饰符表示同时定义在CPU和GPU上,二者都可调用
__global__ void kernel() {
//sayHi();
printf("Block %d of %d, Thread %d of %d\n", blockIdx.x, gridDim.x, threadIdx.x, blockDim.x);
//printf("Thread Numbers %d\n",blockDim.x);
//线程编号
//printf("Thread %d\n",threadIdx.x);
//sayHello();
}
__device__ void sayHi() {
printf("Hi , GPU\n");
}
__host__ void sayHiCpu() {
printf("Hi , CPU\n");
}
__host__ __device__ void sayHello() {
//从GPU上调用就有这个宏
#ifdef __CUDA_ARCH__
printf("Hello , GPU CUDA_ARCH=%d\n", __CUDA_ARCH__);
#else
printf("Hello , CPU NO CUDA_ARCH\n");
#endif
}
一个kernel就是一个网格
网格内有多个板块,板块内有多个线程组
核函数调用时,parm1:板块 parm2:线程数量
#include <iostream>
#include <cuda_runtime.h>
int main() {
kernel<<<2, 3>>>();
kernel<<<dim3(2, 2, 2), dim3(2, 2, 2)>>>();
cudaDeviceSynchronize();
return 0;
}
核函数可以嵌套,相互调用
__global__ void another() {
printf("another : Thread %d of %d\n", threadIdx.x, blockDim.x);
}
__global__ void kernel_1() {
printf("kernel_1 : thread %d of %d\n", threadIdx.x, blockDim.x);
//可以在核函数里调用核函数
another<<<1,3>>>();
}
int main(){
kernel_1<<<2, 3>>>();
return 0;
}
三、内存管理
从核函数中返回数据
由于kernel的调用是异步的,我们创建一个kernel任务函数并调用后,它不会立刻返回,而是将任务提交到GPU的任务队列中,此时GPU并没有将任务执行完,和多线程有点像,所以不能直接从kernel函数中获取相应的返回值
所以核函数的返回类型必须为void
这样是会出错的,不被允许
__global__ int kernel() {
return 100;
}
int main(){
int ret = kernel<<<1, 1>>>();
printf("ret=%d\n", ret);
// kernel<<<dim3(2, 2, 2), dim3(2, 2, 2)>>>();
cudaDeviceSynchronize();
}
如何解决无法获得程序的返回值这个问题呢?
指针?
#include <iostream>
#include <cuda_runtime.h>
#include <common_functions.h>
__global__ void kernel(int *param) {
*param = 100;
}
int main() {
int ret = 0;
kernel<<<1, 1>>>(&ret);
cudaDeviceSynchronize();
printf("ret=%d\n",ret);
return 0;
}
好像不行
通过cudaGetErrorName
获取到底为什么会出错
int ret = 0;
kernel<<<1, 1>>>(&ret);
cudaError_t err= cudaDeviceSynchronize();
printf("error : %s\n",cudaGetErrorName(err));
返回的是非法地址错误,类似于CPU上的段错误
因为这是堆栈上的局部变量,即使使用new
或者malloc
分配的内存,GPU也是访问不到的,因为GPU和CPU上的内存是独立的
GPU操作的其实是显存,当我们传一个地址过去的时候,GPU误以为是显存,而这块地址肯定是没有被分配过的
int main() {
int *ret;
kernel<<<1, 1>>>(cudaMalloc(&ret, sizeof(int)));
cudaError_t err= cudaDeviceSynchronize();
printf("error : %s\n",cudaGetErrorName(err));
printf("ret=%d\n",ret);
cudaFree(ret);
return 0;
}
cudaMalloc
可以实现申请GPU显存
可是CPU却访问不了显存的呀,所以这种方式也不行
解决办法
__global__ void kernel(int *param) {
*param = 100;
}
int main() {
int *ret; //这里通过一个指针存放显存地址
cudaMalloc(&ret,sizeof(int)); //将显存指针的地址传过去,传二级指针,使得指针可以指向一个显存地址
kernel<<<1,1>>>(ret);
cudaError_t err = cudaDeviceSynchronize();
printf("error : %s\n", cudaGetErrorName(err));
int retS; //cpu上的内存
//参数顺序是从右往左拷贝
cudaMemcpy(&retS,ret, sizeof(int),cudaMemcpyDeviceToHost);//将GPU上的内容拷贝到CPU
err = cudaDeviceSynchronize();
printf("error : %s\n", cudaGetErrorName(err));
printf("ret=%d\n", retS);
cudaFree(ret);
return 0;
}
cudaMemcpy
会自动进行同步,所以上面代码的两个cudaDeviceSynchronize
可以去掉
统一内存管理
使用cudaMallocManaged
函数
int *ret;
cudaError_t err = cudaMallocManaged(&ret, sizeof(int));
kernel<<<1, 1>>>(ret);
cudaDeviceSynchronize();
if (err != cudaSuccess) {
printf("Error:%s\n", cudaGetErrorName(err));
return 0;
}
printf("ret=%d\n", *ret);
cudaFree(ret);
return 0;
比较新的显卡支持的特性,只将cudaMalloc
换成cudaMallocManaged
即可,这样分配出来的内存地址,无论在GPU和CPU上都是一样的,而且二者会同步变化,大大方便了开发人员。当然,这是有开销的
数组的分配
和平时的CPU分配数组差不多,只是需要替换成对应的API
__global__ void kernel(int *arr, int arrLen) {
for (int i = 0; i < arrLen; ++i) {
arr[i] = i;
}
}
int main(){
int *ret;
int arrLen = 100;
cudaError_t err = cudaMallocManaged(&ret, arrLen * sizeof(int));
kernel<<<1, 1>>>(ret, arrLen);
cudaDeviceSynchronize();
if (err != cudaSuccess) {
printf("Error:%s\n", cudaGetErrorName(err));
return 0;
}
for (int i = 0; i < arrLen; ++i)
printf("arr[%d]=%d\n", i, ret[i]);
cudaFree(ret);
return 0;
}
使用cudaMalloc
就应该是这样
int main() {
int *ret;
int arrLen = 100;
//在GPU上申请数组内存
cudaMalloc(&ret, arrLen * sizeof(int));
//初始化数组
kernel<<<1, 1>>>(ret, arrLen);
cudaError_t err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
printf("Error:%s\n", cudaGetErrorName(err));
return 0;
}
//在CPU上申请对应的内存并将GPU上的内容拷贝
int *retS = (int *) malloc(arrLen * sizeof(int));
err = cudaMemcpy(retS, ret, arrLen * sizeof(int), cudaMemcpyDeviceToHost);
if (err != cudaSuccess) {
printf("Error:%s\n", cudaGetErrorName(err));
return 0;
}
for (int i = 0; i < arrLen; ++i)
printf("arr[%d]=%d\n", i, retS[i]);
cudaFree(ret);
free(retS);
return 0;
}
使用多线程对数组进行初始化
__global__ void kernel(int *arr, int arrLen) {
int i = threadIdx.x;
arr[i] = threadIdx.x;
}
int main() {
int *ret;
int arrLen = 100;
cudaError_t err = cudaMallocManaged(&ret, arrLen * sizeof(int));
//启动100个线程,给数组赋上线程序号
kernel<<<1, arrLen>>>(ret, arrLen);
cudaDeviceSynchronize();
if (err != cudaSuccess) {
printf("Error:%s\n", cudaGetErrorName(err));
return 0;
}
for (int i = 0; i < arrLen; ++i)
printf("arr[%d]=%d\n", i, ret[i]);
cudaFree(ret);
return 0;
由于<<<m,n>>>参数n太大可能会出错,因为不允许太多的线程,所以可以这样
__global__ void kernel(int *arr, int arrLen) {
//扁平化线程数量处理,网格跨步循环
for (int i = blockDim.x * blockIdx.x + threadIdx.x; i < arrLen; i += blockDim.x * gridDim.x) {
arr[i] = i;
printf("i=%d\n", i);
}
}
int main(){
int *ret;
int N = 6553213;
int blockNum = 312;
int threadNum = (N + blockNum - 1) / N; //向上取整
cudaError_t err = cudaMallocManaged(&ret, N * sizeof(int));
kernel<<<blockNum, threadNum>>>(ret, N);
err = cudaDeviceSynchronize();
for (int i = 0; i < N; ++i) {
printf("arr[%d] = %d\n", i, ret[i]);
}
cudaFree(ret);
}
这样无论调用多少板块多少线程多少网格,都可以运行了
C++编程笔记(GPU并行编程)的更多相关文章
- 五 浅谈CPU 并行编程和 GPU 并行编程的区别
前言 CPU 的并行编程技术,也是高性能计算中的热点,也是今后要努力学习的方向.那么它和 GPU 并行编程有何区别呢? 本文将做出详细的对比,分析各自的特点,为将来深入学习 CPU 并行编程技术打下铺 ...
- 第五篇:浅谈CPU 并行编程和 GPU 并行编程的区别
前言 CPU 的并行编程技术,也是高性能计算中的热点,也是今后要努力学习的方向.那么它和 GPU 并行编程有何区别呢? 本文将做出详细的对比,分析各自的特点,为将来深入学习 CPU 并行编程技术打下铺 ...
- 三 GPU 并行编程的运算架构
前言 GPU 是如何实现并行的?它实现的方式较之 CPU 的多线程又有什么分别?本文将做一个较为细致的分析. GPU 并行计算架构 GPU 并行编程的核心在于线程,一个线程就是程序中的一个单一指令流, ...
- 第三篇:GPU 并行编程的运算架构
前言 GPU 是如何实现并行的?它实现的方式较之 CPU 的多线程又有什么分别? 本文将做一个较为细致的分析. GPU 并行计算架构 GPU 并行编程的核心在于线程,一个线程就是程序中的一个单一指令流 ...
- 四 GPU 并行编程的存储系统架构
前言 在用 CUDA 对 GPU 进行并行编程的过程中,除了需要对线程架构要有深刻的认识外,也需要对存储系统架构有深入的了解. 这两个部分是 GPU 编程中最为基础,也是最为重要的部分,需要花时间去理 ...
- 第四篇:GPU 并行编程的存储系统架构
前言 在用 CUDA 对 GPU 进行并行编程的过程中,除了需要对线程架构要有深刻的认识外,也需要对存储系统架构有深入的了解. 这两个部分是 GPU 编程中最为基础,也是最为重要的部分,需要花时间去理 ...
- 【并行计算-CUDA开发】GPU并行编程方法
转载自:http://blog.sina.com.cn/s/blog_a43b3cf2010157ph.html 编写利用GPU加速的并行程序有多种方法,归纳起来有三种: 1. 利用现有的G ...
- 大数据学习笔记3 - 并行编程模型MapReduce
分布式并行编程用于解决大规模数据的高效处理问题.分布式程序运行在大规模计算机集群上,集群中计算机并行执行大规模数据处理任务,从而获得海量计算能力. MapReduce是一种并行编程模型,用于大规模数据 ...
- C#并发编程之初识并行编程
写在前面 之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间.近日,这套系统已有阶段性成果,所以准备写一下Parallel ...
- GPU并行编程小结
http://peghoty.blog.163.com/blog/static/493464092013016113254852/ http://blog.csdn.net/augusdi/artic ...
随机推荐
- 02 uniapp/微信小程序 项目day02
一.分类 1.1 页面布局 首先创建cate的分支 定义基本结构,因为是两个需要滚动的区域,所以这里要用到组件 scroll 这个组件如果是y scroll那就要固定高度,x scroll那就要固定宽 ...
- MySQL集群搭建(3)-MMM高可用架构
1 MMM 介绍 1.1 简介 MMM 是一套支持双主故障切换以及双主日常管理的第三方软件.MMM 由 Perl 开发,用来管理和监控双主复制,虽然是双主架构,但是业务上同一时间只允许一个节点进行写入 ...
- Logstash:为 Logstash 日志启动索引生命周期管理
文章转载自:https://elasticstack.blog.csdn.net/article/details/110816948
- 痞子衡嵌入式:一个关于Segger J-Flash在Micron Flash固定区域下载校验失败的故事(SR寄存器BP[x:0]位)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家讲的是一个关于Segger J-Flash在Micron Flash固定区域下载校验失败的故事. 痞子衡最近在支持一个 i.MXRT1170 欧 ...
- [笔记] 兰道定理 Landau's Theorem
兰道定理的内容: 一个竞赛图强连通的充要条件是:把它的所有顶点按照入度d从小到大排序,对于任意\(k\in [0,n-1]\)都不满足\(\sum_{i=0}^k d_i=\binom{k+1}{2} ...
- 利用Java集合实现学生信息的”增删查“
之前学了Java中的集合,打算写一个小程序来消化一下! 那么我们知道,集合相比数组的优点就是可以动态的增加元素,这对比数组来说,十分的便捷: 并且集合为我们封装好一些方法,可以更好的做一些数据操作! ...
- jsp页面重定向后地址栏controller名重复而导致报404错误
今天做ssm项目时遇到了这种错误 看看代码: 无关代码省略... 22 <body> 23 <div id="container"> 24 <ifra ...
- 4.ElasticSearch系列之基本概念
1. 文档 ElasticSearch是面向文档的,文档是所有可搜索数据的最小单位 文档会被序列化成JSON格式,保存在ES中 每个文档都有一个unique ID #查看前10条文档,了解文档格式 P ...
- SSM框架整合图书管理项目
SSM框架整合 1.建立简单的maven项目 2.导入依赖 <?xml version="1.0" encoding="UTF-8"?> <p ...
- 前端框架Vue------>第一天学习(2) v-if
API:https://cn.vuejs.org/v2/api/#key 文章目录 5.条件渲染 5.1 . v-if 5.2 . v-else-if 6 .列表渲染 7 .事件监听 5.条件渲染 5 ...