CUDA编程模型是一个异构模型,需要CPU和GPU协同工作.


host和device

host和device是两个重要的概念

  • host指代CPU及其内存
  • device指代GPU及其内存

__global__: host调用,device上执行

__device__:device调用,device执行

__host__:host调用, host执行


典型编程流程

  1. 分配host内存,并进行数据初始化
  2. 分配device内存,并从host将数据拷贝到device上
  3. 调用CUDA的核函数在device上完成指定的运算
  4. 将device上的运算结果拷贝到host上
  5. 释放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的更多相关文章

  1. CUDA编程学习笔记2

    第二章 cuda代码写在.cu/.cuh里面 cuda 7.0 / 9.0开始,NVCC就支持c++11 / 14里面绝大部分的语言特性了. Dim3 __host__ __device__ dim3 ...

  2. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  3. Linux Shell编程学习笔记——目录(附笔记资源下载)

    LinuxShell编程学习笔记目录附笔记资源下载 目录(?)[-] 写在前面 第一部分 Shell基础编程 第二部分 Linux Shell高级编程技巧 资源下载 写在前面 最近花了些时间学习She ...

  4. DirectX 11游戏编程学习笔记之8: 第6章Drawing in Direct3D(在Direct3D中绘制)(习题解答)

            本文由哈利_蜘蛛侠原创,转载请注明出处.有问题欢迎联系2024958085@qq.com         注:我给的电子版是700多页,而实体书是800多页,所以我在提到相关概念的时候 ...

  5. 多线程编程学习笔记——async和await(一)

    接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...

  6. 多线程编程学习笔记——async和await(二)

    接上文 多线程编程学习笔记——async和await(一) 三.   对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...

  7. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  8. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  9. 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端

    接上文 多线程编程学习笔记——使用异步IO 二.   编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...

随机推荐

  1. 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命令都 ...

  2. 2019-11-27-WPF-全屏透明窗口

    原文:2019-11-27-WPF-全屏透明窗口 title author date CreateTime categories WPF 全屏透明窗口 lindexi 2019-11-27 09:22 ...

  3. jsonHelper帮助类

    使用前,需引用开源项目类using Newtonsoft.Json 链接:https://pan.baidu.com/s/1htK784XyRCl2XaGGM7RtEg 提取码:gs2n using ...

  4. CMake编译的VS工程,安装时遇到错误:error MSB3073: 命令“setlocal

    错误提示 70>CMake Error at src/base/cmake_install.cmake:63 (file): 70> file INSTALL cannot find 70 ...

  5. 学习shiro第二天

    昨天讲了shiro的认证流程以及代码实现,今天将对这个进行扩展. 因为我们的测试数据是shiro.ini文件中配置的静态数据,但实际上数据应该从数据库中查询出来才合理,因此我们今天讲讲JdbcReal ...

  6. jdbc、Mybatis、Hibernate介绍(非原创)

    文章大纲 一.jdbc介绍二.Mybatis介绍三.Hibernate介绍四.jdbc.Mybatis.Hibernate比较五.参考文章   一.jdbc介绍 1. jdbc编程步骤 (1)加载数据 ...

  7. MyCat教程六:全局序列号-全局主键的自增长

      前面我们介绍了MyCat的分库分表操作,那么同一张表中的数据会被保存在不同的数据库中,那么这就涉及到了主键维护的问题,此时肯定不能使用单个数据库中id自增的方式来处理了,这时我们就可以通过MyCa ...

  8. python使用ftplib模块实现FTP文件的上传下载

    python已经默认安装了ftplib模块,用其中的FTP类可以实现FTP文件的上传下载 FTP文件上传下载 # coding:utf8 from ftplib import FTP def uplo ...

  9. Swagger从入门到放弃

    如何编写基于OpenAPI规范的API文档 简介 Swagger Swagger是一个简单但功能强大的API表达工具.支持的语言种类繁多 使用Swagger生成API,我们可以得到交互式文档,自动生成 ...

  10. Python列表操作与深浅拷贝(6)——列表索引、查询、修改、扩展

    列表list定义 L1 = [] L2 = [1,2,'abc'] L3 = list() L4 = list(range(5)) L5 = [1,'abc',True,None,[4,5,'abc' ...