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. SpringBoot2版本Caused by: java.sql.SQLSyntaxErrorException: Table 'dinner.hibernate_sequenc

    1.SpringBoot2版本Caused by: java.sql.SQLSyntaxErrorException: Table 'dinner.hibernate_sequenc报错. -java ...

  2. 网格弹簧质点系统模拟(Spring-Mass System by Fast Method)附源码(转载)

    转载:  https://www.cnblogs.com/shushen/p/5311828.html 弹簧质点模型的求解方法包括显式欧拉积分和隐式欧拉积分等方法,其中显式欧拉积分求解快速,但积分步长 ...

  3. aspx.designer.cs没有自动生成代码(没有自动注册)

    遇到这个问题的最大可能是:aspx页面存在bug. 比如说我的主页是从项目里的别的页面复制过来的,但是少复制了一些引用,页面就存在bug,导致aspx.designer.cs没有自动生成代码. 解决方 ...

  4. Server2016 IIS安装配置

  5. JPA笔记4 ManyToMany

    package many_to_many; import java.util.HashSet; import java.util.Set; import javax.persistence.Entit ...

  6. 【MySQL高可用架构设计】(一)-- mysql复制功能介绍

    一. 介绍 Mysql的复制功能是构建基于SQL数据库的大规模高性能应用的基础,主要用于分担主数据库的读负载,同时也为高可用.灾难恢复.备份等工作提供了更多的选择. 二.为什么要使用mysql复制功能 ...

  7. 简单的深度神经网络实现——使用PyTorch

    使用的数据集是MNIST,预期可以达到98%左右的准确率. 该神经网络由一个输入层,一个全连接层结构的隐含层和一个输出层构建. 1.配置库和配置参数 import torch import torch ...

  8. py2,py3区别 ,基础网络常识,多线程,

    1.编译型:把代码编译成机器码,计算机找虚拟机执行代码,机器码交给计算技巧去运行 :C语言,java c# 解释型:边解释边执行 5.只有列表才能reverse 字符串需要索引[::-1] 6.py2 ...

  9. JS高阶---原型面试

    测试1: 分析如下 结果如下: 测试2: 测试3: 测试4: .

  10. 201871010112-梁丽珍《面向对象程序设计(java)》第十五周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...