1. 典型的CUDA编程包括五个步骤:

  1. 分配GPU内存

  2. 从CPU内存中拷贝数据到GPU内存中

  3. 调用CUDA内核函数来完成指定的任务

  4. 将数据从GPU内存中拷贝回CPU内存中

  5. 释放GPU内存

*2.

数据局部性:(是指数据重用,以降低对于内存访问的延迟)

  1. 时间局部性:指在较短的时间内实现对数据或资源的重用

  2. 空间局部性:指在相对较接近的存储空间内数据元素的重用

CPU中通过缓存来增强时间局部性和空间局部性的优化

(不是很懂)3.CUDA中有内存层次和线程层次的概念

  1. 内存层次结构

  2. 线程层次结构

CUDA编程模型中存在共享内存,通过为主内存节省带宽来大幅度提高运行速度。

4.在CUDA编程中,你只需要编写单个线程需要执行的操作命令,通过调用核函数,GPU通过处理这样的核函数,来启动成千上万的线程来运行这个操作。

5.CUDA开发环境

  • NVIDIA Nsight 集成开发环境

  • CUDA-GDB命令行调试器

  • 用于性能分析的可视化和命令行分析器

  • CUDA-MEMCHECK内存分析器

  • GPU设备管理工具

6.CUDA编程模型利用GPU架构的计算能力提供了以下几个特有功能:

  • 一种通过层次结构在GPU中组织线程的方法

  • 一种通过层次结构在GPU中访问内存的方法

7.在使用CUDA编程模型的时候,需要注意到

  • 主机:CPU及其内存

  • 设备:GPU及其内存(GPU显存)

8.CUDA6.0开始,NVIDIA推出统一寻址方式,可以通过指针访问主机内存和设备内存

9.内核是CUDA编程模型的重要组成部分,其代码在GPU设备上运行。

10.多数情况下,主机可以独立地对设备进行操作,内核一旦启动,管理权立刻返回给主机,一个典型的CUDA程序包括由并行代码互补的串行代码。

11.CUDA编程模型假设系统是由一个主机和一个设备组成的,而且各自拥有独立的内存,核函数是在设备上运行的,CUDA运行时负责分配和释放设备上的内存,并且在主机内存和设备内存之间进行传输。

12.用于分配设备内存的CUDA函数是

cudaError_t cudaMalloc(void ** devPtr,size_t size);

当分配好内存后,该函数会返回devPtr指针用来指向分配的内存,size是分配内存的大小。

cudaError_t cudaFree(void * devPtr)

用于释放GPU上的内存。

13.用于拷贝主机和设备之间数据的函数是

cudaError_t cudaMemcpy(void * dst,const void * src,size_t count,cudaMemcpyKind kind)

此函数从src指向的存储空间复制一定字节数的数据到由dst指定的数据存储区。count表示复制的字节数量,kind表示复制的方向,有:

  • cudaMemcpyHostToDevice(主机到设备)

  • cudaMemcpyDeviceToHost(设备到主机)

  • cudaMemcpyDeviceToDevice(设备到设备)

  • cudaMemcpyHostToHost(主机到主机)

注意这个函数是以同步方式执行的,在复制还没完成的时候此时主机的程序是被阻塞的,当这个函数执行完成以后主机程序和核函数才会继续运行,除了内核启动之外的CUDA函数都会返回一个错误的枚举类型:cudaError_t,内存分配成功则返回:

cudaSuccess

否则返回:

cudaErrorMemoryAllocation

可以使用以下函数将错误代码转换为错误的可读信息:

char * cudaGetErrorString(cudaError_t error);

14.nvcc 封装了几种内部编译器,CUDA编译器允许通过命令行选项启动不同的工具完成编译操作。当存在一个完全在主机上运行的C语言文件时,也可以通过nvcc来进行编译:

nvcc -Xcompiler -std=c99 hello.c -o hello

-Xcompiler用于制定命令行选项是指向C编译器还是预处理器,-std=c99表示使用什么样的标准来对代码进行处理。

(线程管理)15.当核函数在主机端启动时,它的执行会被移动到设备端,此时设备中会产生大量的线程,并且每个线程都会完成由核函数指定的操作。CUDA明确了线程抽象的概念,

这是一个两层的线程层次结构: 由线程块和线程块网格构成,线程网格内部含有线程块,线程块内部含有线程。

16.由一个内核启动所产生的所产生的线程为同一个网格,同一个线程块中的所有线程共享同一个全局内存空间,一个网格内部包含多个线程块,一个线程块中包含着一组线程,线程块中的线程可以通过两种方式相互协作:

  • 同步

  • 共享内存

不同线程块的线程不能进行相互协作。

线程依靠两个坐标变量来进行区分索引

  • blockIdx(线程块在线程网格内的索引位置)

  • threadIdx(线程在线程块内的索引位置)

这些变量是一个核函数需要初始化的内置变量,当执行一个核函数时,CUDA运行时为每个线程分配坐标变量(blockIdx,threadIdx),你可以将部分数据分配给不同的线程。

blockIdx,threadIdx两个变量是基于uint3定义的CUDA内置向量类型,可以通过x,y,z三个字段来指定:

  • blockIdx.x

  • blockIdx.y

  • blockIdx.z

  • threadIdx.x

  • threadIdx.y

  • threadIdx.z

17.CUDA可以组织三维的线程网格和线程块,网格和块的维度由两个dim3变量决定

  • blockDim(线程块的维度,表示一个线程块中的线程的个数)

  • gridDim(线程格的维度,表示一个线程格中的线程个数)

当定义一个dim3变量时,其中的未定义的变量默认为1,dim3中的每个组件都可以通过x,y,z获得

  • blockDim.x

  • blockDim.y

  • blockDim.z

通常情况下,一个线程格会被组织成线程块的二维数组的形式,而线程块会被组织成为线程的三维数组形式

18. 在主机端,可以通过定义dim3类型的数据自定义一个线程格和线程块的维度,当执行核函数时,会自动生成相应的内置初始化网格和块。

19.定义线程块维度和线程网格维度:

  • 首先定义一个较小的数据

int nElement = 6;

  • 其次定义线程块维度

dim3 block(3);

  • 定义线程格维度

dim3 grid((nElement + block.x – 1)/(block.x));(必须这么定义)

网格大小是块大小的倍数.

20.输出每个线程的threadIdx和blockIdx的源码:

#include <cuda_runtime.h>

#include <stdlib.h>

#include <string.h>

#include <stdio.h>

__global__ void print_Id(void){

printf("threadIdx:(%d,%d,%d),blockIdx:(%d,%d,%d),blockDim:(%d,%d,%d),gridDim:(%d,%d,%d)\n",threadIdx.x,threadIdx.y,threadIdx.z,blockIdx.x,blockIdx.y,blockIdx.z,blockDim.x,blockDim.y,blockDim.z,gridDim.x,gridDim.y,gridDim.z);

}

int main(){

int nElement = 6;

cudaSetDevice(0);

dim3 block(3);

dim3 grid((nElement + block.x - 1)/ block.x);

printf("Host : block: (%d,%d,%d),grid:
(%d,%d,%d)\n",block.x,block.y,block.z,grid.x,grid.y,grid.z);

print_Id<<< grid,block >>> ();

cudaDeviceReset();

return 0;

}

21.要确定块尺寸,通常需要考虑:

  • 内核的性能特性

  • GPU资源的限制

22.CUDA内核调用是对C语言函数调用语句的延伸,<<<>>>运算符内是核函数的运行配置。

Kernel_name <<<grid,block>>>(argument list)

执行配置的第一个参数是网格维度,也就是线程块的个数,第二个参数是线程块维度,也就是一个线程块中线程的个数。通过制定这两个参数,可以进行以下配置:

  • 内核中线程的数目

  • 内核中使用的线程的布局

23.核函数调用结束后控制权立刻返回主机端,它的调用与主机线程之间是异步的,要实现核函数与主机端的同步,可使用

cudaError_t cudaDeviceSynchronize(void);

cudaMemcpy函数只有当核函数计算完成后才开始进行数据的传输

24.函数类型限定符:

  • __global__
    函数执行时在设备端执行,调用时在主机端调用或者在计算能力大于3.0的设备端调用,这类函数要求返回类型要为void型。

  • __device__ 函数在设备端执行,且只能在设备端调用

  • __host__ 函数在主机端调用并执行,一般情况下可以忽略不记

25.CUDA核函数的限制

  • 只能访问设备内存(不认可)

  • 必须具有void返回类型(不认可)

  • 不支持可变数量的参数

  • 不支持静态变量

  • 显示异步行为

26.CUDA中实现对向量的相加操作源码:

__global__
void add_vec(float *A,float *B,float *C){

int
count = threadIdx.x;

C[count]
= A[count] + B[count];

}

27.验证核函数

除了调试工具外,还可以使用printf(argument
list)函数来进行验证,

或者在配置过程<<<1,1>>>模拟串行进行编译,同时这也有助于验证数值是否按位计算

28.两个数组相加源码:

#include<stdio.h>

#include<cuda_runtime.h>

voidCHECK(const
cudaError_t error){

if(error!=
cudaSuccess)

{

printf("error:%s",cudaGetErrorString(error));

exit(1);

}

}

__global__voidarray_add_GPU(float
*A,float
*B,float
*C,size_t
count){

int
index_count = blockIdx.x * blockDim.x + threadIdx.x;

if(index_count
< count)

{

C[index_count]
= A[index_count]+B[index_count];

printf("threadIdx:%d,result
is %lf\n",threadIdx.x,C[index_count]);

}

}

voidinitial_data(float
*A,size_t
num){

for
(int
i = 0; i< num; i++)

{

A[i]
= i;

}

}

__host__voidshow_data_host(float
*show_data,size_t
n){

for
(int
index_count = 0;index_count < n;index_count ++)

{

printf("Host
Show : %lf\n",show_data[index_count]);

}

}

intmain(){

printf("Starting..\n");

cudaSetDevice(0);

float
*A,*B,*C;

int
nBytes = sizeof(float)
* 32;

A
=(float
*) malloc(nBytes);

B
=(float
*) malloc(nBytes);

C
=(float
*) malloc(nBytes);

initial_data(A,32);

initial_data(B,32);

//int
nBytes = sizeof(float)
* 32;

float
*d_A,*d_B,*d_C;

CHECK(cudaMalloc((float
**) &d_A,nBytes));

printf("debug\n");

cudaMalloc((float
**) &d_B,nBytes);

cudaMalloc((float
**) &d_C,nBytes);

cudaMemcpy((float
*)d_A,(float
*)A,nBytes,cudaMemcpyHostToDevice);

cudaMemcpy((float
*)d_B,(float
*)B,nBytes,cudaMemcpyHostToDevice);

array_add_GPU<<<1,512>>>(d_A,d_B,d_C,32);

cudaDeviceSynchronize();

cudaMemcpy((float
*)C,(float
*)d_C,nBytes,cudaMemcpyDeviceToHost);

show_data_host(C,32);

cudaFree(d_C);

cudaFree(d_A);

cudaFree(d_B);

free(A);

free(B);

free(C);

cudaDeviceReset();

return
0;

}

29.如果使用了合适的块大小和网格大小来正确地组织线程,那么会对性能进行极大的优化。

*30.矩阵运算,传统的方法是对CUDA使用一个包含二维网格与二维块的布局来组织线程,还可以使用如下布局:

  • 由二维线程块构成的二维网格

  • 由一维线程块构成的一维网格

  • 由一位线程块构成的二维网格

31.通常情况下,一个矩阵的存储方式通常是行优先的方式在全局内存中进行线性存储。

*32.对于一个矩阵来说通常需要三种方式索引:

  1. 线程和块索引

  2. 矩阵中给定点的坐标

  3. 全局限性内存中的偏移量

*33.首先,通过以下公式把块索引和线程索引映射到矩阵的坐标上

ix
= blockIdx.x * blockDim.x + threadIdx.x

iy
= blockIdx.y * blockDim.y + threadIdx.y

其次,将矩阵坐标映射到全局内存的索引上:

idx
= iy * nx + ix

35.使用二维网格和一维块进行矩阵求和操作,即一个块针对一个数据进行优化,那么此时

ix
= threadIdx.x + blockDim.x * blockIdx.x

iy
= blockIdx.y;

将矩阵坐标映射到全局内存的索引上:

idx
= iy * nx + ix;

通过上述的几个不同的核函数配置和线程块和网格的布局不同可以认为:

  • 改变执行配置对内核性能会产生影响

  • 传统的核函数不一定能够达到最优性能

  • 对于一个核函数,可以进行多次尝试找到最优性能

36.对于核函数的调试,当发现核函数无法进入等意外出现的时候,可以使用

cudaError_t cudaGetLastError();

获得,将该函数放置在核函数运行的下方,函数将会返回CUDA中所产生的错误。

CUDA编程模型的更多相关文章

  1. CUDA编程模型之内存管理

    CUDA编程模型假设系统是由一个主机和一个设备组成的,而且各自拥有独立的内存. 主机:CPU及其内存(主机内存),主机内存中的变量名以h_为前缀,主机代码按照ANSI C标准进行编写 设备:GPU及其 ...

  2. CUDA刷新器:CUDA编程模型

    CUDA刷新器:CUDA编程模型 CUDA Refresher: The CUDA Programming Model CUDA,CUDA刷新器,并行编程 这是CUDA更新系列的第四篇文章,它的目标是 ...

  3. CUDA学习笔记(一)——CUDA编程模型

    转自:http://blog.sina.com.cn/s/blog_48b9e1f90100fm56.html CUDA的代码分成两部分,一部分在host(CPU)上运行,是普通的C代码:另一部分在d ...

  4. CUDA编程模型——组织并行线程3 (2D grid 1D block)

    当使用一个包含一维块的二维网格时,每个线程都只关注一个数据元素并且网格的第二个维数等于ny,如下图所示: 这可以看作是含有二维块的二维网格的特殊情况,其中块儿的第二个维数是1.因此,从块儿和线程索引到 ...

  5. CUDA编程模型——组织并行线程2 (1D grid 1D block)

    在”组织并行编程1“中,通过组织并行线程为”2D grid 2D block“对矩阵求和,在本文中通过组织为 1D grid 1D block进行矩阵求和.一维网格和一维线程块的结构如下图: 其中,n ...

  6. cuda编程基础

    转自: http://blog.csdn.net/augusdi/article/details/12529247 CUDA编程模型 CUDA编程模型将CPU作为主机,GPU作为协处理器(co-pro ...

  7. CUDA编程之快速入门

    CUDA(Compute Unified Device Architecture)的中文全称为计算统一设备架构.做图像视觉领域的同学多多少少都会接触到CUDA,毕竟要做性能速度优化,CUDA是个很重要 ...

  8. CUDA编程学习笔记1

    CUDA编程模型是一个异构模型,需要CPU和GPU协同工作. host和device host和device是两个重要的概念 host指代CPU及其内存 device指代GPU及其内存 __globa ...

  9. CUDA编程之快速入门【转】

    https://www.cnblogs.com/skyfsm/p/9673960.html CUDA(Compute Unified Device Architecture)的中文全称为计算统一设备架 ...

随机推荐

  1. C# 断言 Assert

    重构-断言 现象:某一段代码需要对程序状态做出某种假设 做法:以断言明确表现这种假设 动机: 常常有这种一段代码:只有某个条件为真是,该改名才能正常运行. 通常假设这样的假设并没有代码中明确表现出来, ...

  2. Codeforces 558E A Simple Task (计数排序&&线段树优化)

    题目链接:http://codeforces.com/contest/558/problem/E E. A Simple Task time limit per test5 seconds memor ...

  3. SecureCRT标签显示IP地址

    当使用SecureCRT连接到linux服务器后,SecureCRT的标签会随着操作目录的改变而改变,当连接多个的时候很不好区分,所以需要设置标签栏固定显示IP地址信息. options->Se ...

  4. No grammar constraints (DTD or XML Schema) referenced in the document.

    问题描述 web.xml 使用 Servlet4.0 版本,No grammar constraints (DTD or XML Schema) referenced in the document. ...

  5. (详细)华为P8 GRA-UL00的Usb调试模式在哪里开启的方法

    经常我们使用Pc通过数据线连接上安卓手机的时候,如果手机没有开启usb开发者调试模式,Pc则没能够成功检测到我们的手机,有时候我们使用的一些功能较强的应用软件好比之前我们使用的一个应用软件引号精灵,老 ...

  6. JavaFX 记录刚刚接触JavaFX遇到的问题

    操作系统:Windows 10 x64 工具1:Eclipse Java EE IDE for Web Developers. Version: Photon Release (4.8.0) JDK: ...

  7. .net基础学java系列(七)赶鸭子上架看项目代码

    项目用到的技术栈 序列化 com.alibaba.fastjson.JSON; https://github.com/alibaba/fastjson/wiki/Quick-Start-CN 日志 l ...

  8. UWP Acrylic Material

    文档:https://docs.microsoft.com/en-us/windows/uwp/design/style/acrylic Acrylic 能带来类似 win7 的毛玻璃效果 要使用 A ...

  9. pypi pack and upload

    upload 403 error need to change the name in setup.py upload 400 error need to change the version of ...

  10. HangFire快速入门 分布式后端作业调度框架服务

    安装 NuGet 上有几个可用的Hangfire 的软件包.如果在ASP.NET应用程序中安装HangFire,并使用Sql Server作为存储器,那么请在Package Manager Conso ...