CUDA编程学习笔记1
CUDA编程模型是一个异构模型,需要CPU和GPU协同工作.
host和device
host和device是两个重要的概念
- host指代CPU及其内存
- device指代GPU及其内存
__global__
: host调用,device上执行
__device__
:device调用,device执行
__host__
:host调用, host执行
典型编程流程
- 分配host内存,并进行数据初始化
- 分配device内存,并从host将数据拷贝到device上
- 调用CUDA的核函数在device上完成指定的运算
- 将device上的运算结果拷贝到host上
- 释放device和host上的内存
核函数
核函数(kernel)是在device上线程中并行的函数.
- 初始化:核函数用
__global__
符号声明 - 每一个线程有唯一的县称号thread ID,这个用内置变量
threadIdx
- 在调用时候用
<<<grid,block>>>
来指定kernel要执行的线程数量.
其中,一个kernel所启用的所有的线程称为grid,同一个grid上的线程共享相同的全局内存空间,grid又可以分割为很多的block,block里包含很多线程.
dim3 grid(3, 2);
dim3 block(5, 3);
kernel_fun<<<grid, block>>>(params...);
内存模型
- 每个线程有自己的local memory
- 每个线程块(block)有shared memory.可以block中所有的thread共享,其生命周期与block一致.
- 所有的thread都可以访问全局内存global memory.还可以访问一些只读模块,constant memory 和 texture memory.
GPU硬件实现的基本认识
一个kernel会启动很多线程,这些线程逻辑上是并行的,但是在物理上却不一定.这个和CPU的多线程有类似支出,多线程如果没有多核支持,在物理层也是无法实现的.
但是好在GPU存在很多CUDA核心,充分利用CUDA核心可以充分发挥GPU的并行计算能力.
GPU硬件的一个核心组件是SM(streaming multiprocessor),流式多处理器.
SM的核心组件包括CUDA核心,共享内存,寄存器等.
一个线程块只能在一个SM上被调度。SM一般可以调度多个线程块,这要看SM本身的能力。
那么有可能一个kernel的各个线程块被分配多个SM,所以grid只是逻辑层,而SM才是执行的物理层。
由于SM的基本执行单元是包含32个线程的线程束,所以block大小一般要设置为32的倍数。
在进行CUDA编程前,可以先检查一下自己的GPU的硬件配置,这样才可以有的放矢,可以通过下面的程序获得GPU的配置属性
int dev = 0;
cudaDeviceProp devProp;
CHECK(cudaGetDeviceProperties(&devProp, dev));
std::cout << "使用GPU device " << dev << ": " << devProp.name << std::endl;
std::cout << "SM的数量:" << devProp.multiProcessorCount << std::endl;
std::cout << "每个线程块的共享内存大小:" << devProp.sharedMemPerBlock / 1024.0 << " KB" << std::endl;
std::cout << "每个线程块的最大线程数:" << devProp.maxThreadsPerBlock << std::endl;
std::cout << "每个EM的最大线程数:" << devProp.maxThreadsPerMultiProcessor << std::endl;
std::cout << "每个EM的最大线程束数:" << devProp.maxThreadsPerMultiProcessor / 32 << std::endl;
// 输出如下
使用GPU device 0: GeForce GT 730
SM的数量:2
每个线程块的共享内存大小:48 KB
每个线程块的最大线程数:1024
每个EM的最大线程数:2048
每个EM的最大线程束数:64
加法实例
cudaError_t cudaMalloc(void** devPtr, size_t size);
cudaError_t cudaMemcpy(void* dist, const void* src, size_t count, cudaMemcpyKind kind);
其中cudaMemcpyKind
是一个enum
enum cudaMemcpyKind {
cudaMemcpyHostToHost,
cudaMemcpyHostToDevice,
cudaMemcpyDeviceToHost,
cudaMemcpyDeviceToDevice
};
// -- grid 和 block 都是1-dim, 先定义kernel
__global__ void add(float* x, float* y, float* z, int n) {
int index = threadIdx.x + blockIdx.x * blockDim*x;
int stride = blockDim.x * gridDim.x; // -- 整个grid的总线程数
for (int i = index; i < n; i += stride) {
z[i] = x[i] + y[i];
}
}
int main() {
int N = 1 << 20;
int nBytes = N * sizeof(float);
// 申请host内存
float *x, *y, *z;
x = (float*)malloc(nBytes);
y = (float*)malloc(nBytes);
z = (float*)malloc(nBytes);
// -- init data
for (int i = 0; i < N; ++i) {
x[i] = 10.0;
y[i] = 20.0;
}
// --申请device内存
float *d_x, *d_y, *d_z;
cudaMalloc((void**)&d_z, nBytes);
cudaMalloc((void**)&d_y, nBytes);
cudaMalloc((void**)&d_z, nBytes);
// -- host copy to device
cudaMemcpy((void*)d_x, (void*)x, nBytes, cudaMemcpyHostToDevice);
cudaMemcpy((void*)d_y, (void*)y, nBytes, cudaMemcpyHostToDevice);
// -- 定义kernel的执行配置
dim3 blockSize(256);
dim3 gridSize((N + blockSize.x - 1) / blockSize.x);
// -- 执行kernel
add <<<gridSize, blockSize>>>(d_x, d_y, d_z, N);
// --
}
#include <iostream>
#include <time.h>
#include "opencv2/highgui.hpp"
#include "opencv2/opencv.hpp"
using namespace cv;
using namesapce std;
__global__ void rgb2grayincuda(uchar3* const d_in, unsigned char* const d_out, uint imgheight, uint imgwidth) {
const unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
const unsigned int idy = blocKIdx.y * blockDim.y + threadIdx.y;
if (idx < imgwidth && idy < imgheight) {
uchar3 rgb = d_in[idy * imgwidth + id];
d_out[idy * imgwidth + idx] = 0.229f * rgb.x + 0.587f * rgb.y + 0.114f * rgb.z;
}
}
int main(void) {
Mat srcImage = imread("./test.jpg");
const uint imgheight = srcImage.rows;
const uint imgwidth = srcImage.cols;
Mat grayImage(imgheight, imgwidth, CV_8UC1, Scalar(0));
uchar3 *d_in;
unsighed char * d_out;
cudaMalloc((void**)&d_in, imgheight * imgwidth * sizeof(uchar3));
cudaMalloc((void**)&d_out, imgheight * imgwidht * sizeof(unsigned char));
cudaMemcpy(d_in, srcImage.data, imgheight * imgwidth * sizeof(uchar3), cudaMemcpyHostToDevice);
dim3 threadsPerBlock(32, 32);
dim3 blocksPerGrid((imgwidth + threadsPerBlock.x - 1) / (threadPerBlock.x,, (imgheight + threadPerBlock.y - 1) / threadsPerBlock.y);
rgb2grayincuda <<<blocksPerGrid, threadsPerBlock>>>(d_in, d_out, imgheight, imgwidth);
cudaDeviceSynchronize();
}
CMakeLists.txt
cmake_minumum_requred(VERSION 2.8)
project(testcuda)
find_package(CUDA REQUIRED)
find_package(OpenCV REQUIRED)
cuda_add_executable(testcuda main.cu)
target_link_libraries(testcuda ${OpenCV_LIBS})
设备内存
CUDA运行库提供了函数以分配/释放设备端的内存,以及与主机端内存传输数据。
这里的设备内存,指的是全局内存+常量内存+纹理内存。
线性内存是我们常用的内存方式,在GPU上用40位的地址线寻址。线性内存可以用cudaMalloc()
分配,用cudaFree()
释放,用cudaMemcpy()
复制数据,用cudaMemset()
赋值。
对于2D或3D数组,可以使用cudaMallocPitch()
和cudaMalloc3D()
来分配内存。这两个函数会自动padding,以满足内存对齐的要求,提高内存读写效率。内存对齐的问题,会在第五章里详细阐述。
另外,如果要在设备内存中定义全局变量,则需要使用使用__constant__
或__device__
来修饰,并使用cudaMemcpyToSymbol()
和cudaMemcpyFromSymbol()
来读写。如下例:
__constant__ float constData[256];
float data[256];
cudaMemcpyToSymbol(constData, data, sizeof(data));
cudaMemcpyFromSymbol(data, constData, sizeof(data));
__device__ float devData;
float value = 3.14f;
cudaMemcpyToSymbol(devData, &value, sizeof(float));
__device__ float* devPointer;
float* ptr;
cudaMalloc(&ptr, 256 * sizeof(float));
cudaMemcpyToSymbol(devPoint, &ptr, sizeof(ptr));
实际上,当使用__constant__
关键字时,是申请了一块常量内存;而使用__device__
时,是普通的全局内存。因此__device__
申请的内存需要申请,而__constant__
不用。不管是全局内存,还是常量内存,需要用带有Symbol
的函数拷贝。
Texture
enum cudaTextureAddressMode {
cudaAddressModeWrap, // -- warpping address mode
cudaAddressModeClamp, // -- 将超出坐标截断为最大值或最小值,即返回图像边缘像素值
cudaAddressModeMirror, // -- 将图像看成周期函数访问
cudaAddressModeBorder // -- 如果超出边缘就返回0
};
enum cudaTextureFilterMode {
cudaFilterModePoint, // -- point filter mode 最近领插值
cudaFilterModeLinear // -- linear filter mode 双线性插值 必须配合float使用
};
enum cudaTextureReadMode {
cudaReadModeElementType, // -- read texture as specifed element type
cudaReadModeNormalizedFloat // -- read texture as normalized float
}
纹理的声明
texture<Datatype, Type, ReadMode> texRef;
// Datatype, 数据类型, uchar, float, double
// Type, 纹理维度, Type = 2(二维)
// ReadMode, 访问模式,
enum cudaTextureFilterMode filterMode;
关于cudaMalloc
cudaError_t cudaMalloc(void ** devPtr, size_t size);
cudaError_t cudaMalloc3D(struct cudaPitchedPtr* pitchedDevPtr, struct cudaExtext extext);
cudaError_t cudaMallocArray(struct cudaArray** array, const struct cudaChannelFormatDesc* desc, size_t width, size_t height, unsigned int flags = 0);
CUDA编程学习笔记1的更多相关文章
- CUDA编程学习笔记2
第二章 cuda代码写在.cu/.cuh里面 cuda 7.0 / 9.0开始,NVCC就支持c++11 / 14里面绝大部分的语言特性了. Dim3 __host__ __device__ dim3 ...
- JAVA GUI编程学习笔记目录
2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...
- Linux Shell编程学习笔记——目录(附笔记资源下载)
LinuxShell编程学习笔记目录附笔记资源下载 目录(?)[-] 写在前面 第一部分 Shell基础编程 第二部分 Linux Shell高级编程技巧 资源下载 写在前面 最近花了些时间学习She ...
- DirectX 11游戏编程学习笔记之8: 第6章Drawing in Direct3D(在Direct3D中绘制)(习题解答)
本文由哈利_蜘蛛侠原创,转载请注明出处.有问题欢迎联系2024958085@qq.com 注:我给的电子版是700多页,而实体书是800多页,所以我在提到相关概念的时候 ...
- 多线程编程学习笔记——async和await(一)
接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...
- 多线程编程学习笔记——async和await(二)
接上文 多线程编程学习笔记——async和await(一) 三. 对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...
- 多线程编程学习笔记——async和await(三)
接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五. 处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...
- 多线程编程学习笔记——使用异步IO(一)
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端
接上文 多线程编程学习笔记——使用异步IO 二. 编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...
随机推荐
- Centos安装jdk1.8出现-bash: //usr/local/soft/jdk1.8.0_191/bin/javac: /lib/ld-linux.so.2: bad ELF interpreter: 没有那个文件或目录错误。
1.从来没有这么郁闷,之前安装都是好好的,自从将Centos升级到7.0版本,安装了jdk报了这个错误,也是郁闷的一毛,参考了一下百度的,记录一下.使用java命令还有java -version命令都 ...
- 2019-11-27-WPF-全屏透明窗口
原文:2019-11-27-WPF-全屏透明窗口 title author date CreateTime categories WPF 全屏透明窗口 lindexi 2019-11-27 09:22 ...
- jsonHelper帮助类
使用前,需引用开源项目类using Newtonsoft.Json 链接:https://pan.baidu.com/s/1htK784XyRCl2XaGGM7RtEg 提取码:gs2n using ...
- CMake编译的VS工程,安装时遇到错误:error MSB3073: 命令“setlocal
错误提示 70>CMake Error at src/base/cmake_install.cmake:63 (file): 70> file INSTALL cannot find 70 ...
- 学习shiro第二天
昨天讲了shiro的认证流程以及代码实现,今天将对这个进行扩展. 因为我们的测试数据是shiro.ini文件中配置的静态数据,但实际上数据应该从数据库中查询出来才合理,因此我们今天讲讲JdbcReal ...
- jdbc、Mybatis、Hibernate介绍(非原创)
文章大纲 一.jdbc介绍二.Mybatis介绍三.Hibernate介绍四.jdbc.Mybatis.Hibernate比较五.参考文章 一.jdbc介绍 1. jdbc编程步骤 (1)加载数据 ...
- MyCat教程六:全局序列号-全局主键的自增长
前面我们介绍了MyCat的分库分表操作,那么同一张表中的数据会被保存在不同的数据库中,那么这就涉及到了主键维护的问题,此时肯定不能使用单个数据库中id自增的方式来处理了,这时我们就可以通过MyCa ...
- python使用ftplib模块实现FTP文件的上传下载
python已经默认安装了ftplib模块,用其中的FTP类可以实现FTP文件的上传下载 FTP文件上传下载 # coding:utf8 from ftplib import FTP def uplo ...
- Swagger从入门到放弃
如何编写基于OpenAPI规范的API文档 简介 Swagger Swagger是一个简单但功能强大的API表达工具.支持的语言种类繁多 使用Swagger生成API,我们可以得到交互式文档,自动生成 ...
- Python列表操作与深浅拷贝(6)——列表索引、查询、修改、扩展
列表list定义 L1 = [] L2 = [1,2,'abc'] L3 = list() L4 = list(range(5)) L5 = [1,'abc',True,None,[4,5,'abc' ...