CUDA运行时 Runtime(二)

一. 概述

下面的代码示例是利用共享内存的矩阵乘法的实现。在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素。如图10所示,Csub等于两个矩形矩阵的乘积:与Csub具有相同行索引的维度A(A.width,block_size)的子矩阵和与Csub具有相同列索引的维度B(block_size,A.width)的子矩阵。为了适应设备的资源,这两个矩形矩阵根据需要被划分为任意多个尺寸块的正方形矩阵,并且计算Csub作为这些正方形矩阵的乘积之和。这些产品中的每一个都是通过首先将两个对应的方阵从全局内存加载到共享内存,其中一个线程加载每个矩阵的一个元素,然后让每个线程计算产品的一个元素来执行的。每个线程将这些产品的结果累积到一个寄存器中,并在完成后将结果写入全局内存。

通过这种方式阻塞计算,我们利用快速共享内存并节省大量全局内存带宽,因为a仅从全局内存读取(B.width/block_size)次,B读取(a.height/block_size)次。

前一个代码示例中的矩阵类型增加了一个跨距字段,这样子矩阵就可以用相同的类型有效地表示。__device_函数用于获取和设置元素,并从矩阵构建任何子矩阵。

// Matrices are stored in row-major order:

// M(row, col) = *(M.elements + row * M.stride + col)

typedef struct {

int width;

int height;

int stride;

float* elements;

} Matrix;

// Get a matrix element

__device__ float GetElement(const Matrix A, int row, int col)

{

return A.elements[row * A.stride + col];

}

// Set a matrix element

__device__ void SetElement(Matrix A, int row, int col,  float value)

{

A.elements[row * A.stride + col] = value;

}

// Get the BLOCK_SIZExBLOCK_SIZE sub-matrix Asub of A that is

// located col sub-matrices to the right and row sub-matrices down

// from the upper-left corner of A

__device__ Matrix GetSubMatrix(Matrix A, int row, int col)

{

Matrix Asub;

Asub.width    = BLOCK_SIZE;

Asub.height   = BLOCK_SIZE;

Asub.stride   = A.stride;

Asub.elements = &A.elements[A.stride * BLOCK_SIZE * row + BLOCK_SIZE * col];

return Asub;

}

// Thread block size

#define BLOCK_SIZE 16

// Forward declaration of the matrix multiplication kernel

__global__ void MatMulKernel(const Matrix, const Matrix, Matrix);

// Matrix multiplication - Host code

// Matrix dimensions are assumed to be multiples of BLOCK_SIZE

void MatMul(const Matrix A, const Matrix B, Matrix C)

{

// Load A and B to device memory

Matrix d_A;

d_A.width = d_A.stride = A.width; d_A.height = A.height;

size_t size = A.width * A.height * sizeof(float);

cudaMalloc(&d_A.elements, size);

cudaMemcpy(d_A.elements, A.elements, size,

cudaMemcpyHostToDevice);

Matrix d_B;

d_B.width = d_B.stride = B.width; d_B.height = B.height;

size = B.width * B.height * sizeof(float);

cudaMalloc(&d_B.elements, size);

cudaMemcpy(d_B.elements, B.elements, size,

cudaMemcpyHostToDevice);

// Allocate C in device memory

Matrix d_C;

d_C.width = d_C.stride = C.width; d_C.height = C.height;

size = C.width * C.height * sizeof(float);

cudaMalloc(&d_C.elements, size);

// Invoke kernel

dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);

dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y);

MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C);

// Read C from device memory

cudaMemcpy(C.elements, d_C.elements, size,

cudaMemcpyDeviceToHost);

// Free device memory

cudaFree(d_A.elements);

cudaFree(d_B.elements);

cudaFree(d_C.elements);

}

// Matrix multiplication kernel called by MatMul()

__global__ void MatMulKernel(Matrix A, Matrix B, Matrix C)

{

// Block row and column

int blockRow = blockIdx.y;

int blockCol = blockIdx.x;

// Each thread block computes one sub-matrix Csub of C

Matrix Csub = GetSubMatrix(C, blockRow, blockCol);

// Each thread computes one element of Csub

// by accumulating results into Cvalue

float Cvalue = 0;

// Thread row and column within Csub

int row = threadIdx.y;

int col = threadIdx.x;

// Loop over all the sub-matrices of A and B that are

// required to compute Csub

// Multiply each pair of sub-matrices together

// and accumulate the results

for (int m = 0; m < (A.width / BLOCK_SIZE); ++m) {

// Get sub-matrix Asub of A

Matrix Asub = GetSubMatrix(A, blockRow, m);

// Get sub-matrix Bsub of B

Matrix Bsub = GetSubMatrix(B, m, blockCol);

// Shared memory used to store Asub and Bsub respectively

__shared__ float As[BLOCK_SIZE][BLOCK_SIZE];

__shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE];

// Load Asub and Bsub from device memory to shared memory

// Each thread loads one element of each sub-matrix

As[row][col] = GetElement(Asub, row, col);

Bs[row][col] = GetElement(Bsub, row, col);

// Synchronize to make sure the sub-matrices are loaded

// before starting the computation

__syncthreads();

// Multiply Asub and Bsub together

for (int e = 0; e < BLOCK_SIZE; ++e)

Cvalue += As[row][e] * Bs[e][col];

// Synchronize to make sure that the preceding

// computation is done before loading two new

// sub-matrices of A and B in the next iteration

__syncthreads();

}

// Write Csub to device memory

// Each thread writes one element

SetElement(Csub, row, col, Cvalue);

}

图10. 共享内存矩阵乘法

二.   页面锁定主机内存             

运行时提供允许使用页锁定(也称为固定)主机内存(而不是malloc()分配的常规可分页主机内存)的函数:

cudaHostAlloc()和cudaFreeHost()分配并释放页面锁定的主机内存;

cudaHostRegister()页锁定malloc()分配的内存范围(有关限制,请参阅参考手册)。

使用页锁定主机内存有几个好处:

对于异步并发执行中提到的某些设备,页面锁定的主机内存和设备内存之间的复制可以与内核执行同时执行。

在某些设备上,页锁定的主机内存可以映射到设备的地址空间,从而无需将其复制到设备内存或从设备内存复制,如映射内存中所述。

在具有前端总线的系统上,如果主机内存被分配为页锁定,则主机内存和设备内存之间的带宽更高,如果另外它被分配为写入合并,则带宽更高,如写入合并内存中所述。

但是,页锁定的主机内存是一种稀缺资源,因此,在页锁定内存中的分配将在可分页内存中的分配之前很长一段时间开始失败。此外,通过减少操作系统可用于分页的物理内存量,消耗过多的页锁定内存会降低总体系统性能。             

注意:页面锁定的主机内存不缓存在非I/O一致的Tegra设备上。此外,非I/O相干Tegra设备不支持cudaHostRegister()。

简单的零拷贝CUDA示例附带了一个关于页面锁定内存api的详细文档。

三. 便携式存储器

页面锁定内存块可以与系统中的任何设备一起使用(有关多设备系统的详细信息,请参阅多设备系统),但默认情况下,使用上面描述的页锁定内存的好处仅与分配块时的当前设备(以及所有设备共享相同的统一地址空间(如果有的话,如统一虚拟地址空间中所述)结合使用。要使这些优势对所有设备都可用,需要通过将标志cudaHostAllocPortable传递给cudaHostAlloc()来分配块,或者通过将标志cudaHostRegisterPortable传递给cudaHostRegister()来锁定页。

四. 写入组合存储器

默认情况下,页锁定的主机内存被分配为可缓存的。通过将标志cudaHostAllocWriteCombined传递给cudaHostAlloc(),可以选择将其分配为写合并。写组合内存释放主机的一级和二级缓存资源,使更多缓存可用于应用程序的其余部分。此外,在跨PCI Express总线传输期间,不会窥探写合并内存,这可以将传输性能提高高达40%。

从主机读取写组合内存的速度非常慢,因此通常应将写组合内存用于主机只写入的内存。

五.   映射内存

也可以通过传递标记cudahostallocmaped to cudaHostAlloc()或传递标记cudahostragistermapped
to cudahostratrigister()将页锁定主机内存块映射到设备的地址空间。因此,这样的块通常有两个地址:一个在主机内存中,由cudaHostAlloc()或malloc()返回;另一个在设备内存中,可以使用cudaHostGetDevicePointer()检索,然后用于从内核中访问块。唯一的例外是使用cudaHostAlloc()分配的指针,以及在统一虚拟地址空间中为主机和设备使用统一地址空间时。

直接从内核中访问主机内存不会提供与设备内存相同的带宽,但确实有一些优点:

l  不需要在设备内存中分配一个块并在这个块和主机内存中的块之间复制数据;数据传输是根据内核的需要隐式执行的;

l  不需要使用流(参见并发数据传输)来将数据传输与内核执行重叠;内核发起的数据传输会自动与内核执行重叠。

但是,由于映射的页锁定内存在主机和设备之间共享,应用程序必须使用流或事件同步内存访问(请参阅异步并发执行),以避免任何潜在的读后写、读后写或写后写危险。

要能够检索指向任何映射的页锁定内存的设备指针,在执行任何其他CUDA调用之前,必须通过使用cudaDeviceMapHost标志调用cudaSetDeviceFlags()来启用页锁定内存映射。否则,cudaHostGetDevicePointer()将返回错误。

如果设备不支持映射页锁定的主机内存,cudaHostGetDevicePointer()也会返回错误。应用程序可以通过检查canMapHostMemory设备属性(请参阅设备枚举)来查询此功能,对于支持映射页锁定主机内存的设备,该属性等于1。

请注意,从主机或其他设备的角度来看,在映射页锁定内存上运行的原子函数(请参阅原子函数)不是原子函数。

还要注意,CUDA运行时要求从主机和其他设备的角度,将从设备启动的1字节、2字节、4字节和8字节自然对齐的加载和存储保存为单个访问。在某些平台上,内存原子可能会被硬件分解为单独的加载和存储操作。这些组件加载和存储操作对保持自然对齐的访问具有相同的要求。例如,CUDA运行时不支持PCI Express总线拓扑,其中PCI Express网桥在设备和主机之间将8字节自然对齐的写入拆分为两个4字节的写入。

CUDA运行时 Runtime(二)的更多相关文章

  1. CUDA运行时 Runtime(一)

    CUDA运行时 Runtime(一)             一. 概述 运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和libcudart.a,或动态通过cuda ...

  2. CUDA运行时 Runtime(四)

    CUDA运行时 Runtime(四) 一.     图 图为CUDA中的工作提交提供了一种新的模型.图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义.这允许定义一次图形,然后重复启动.将 ...

  3. CUDA运行时 Runtime(三)

    CUDA运行时 Runtime(三) 一.异步并发执行 CUDA将以下操作公开为可以彼此并发操作的独立任务: 主机计算: 设备计算: 从主机到设备的内存传输: 从设备到主机的存储器传输: 在给定设备的 ...

  4. Deep Learning部署TVM Golang运行时Runtime

    Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...

  5. iOS运行时Runtime浅析

    运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...

  6. “ compiler-rt”运行时runtime库

    " compiler-rt"运行时runtime库 编译器-rt项目包括: Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口. ...

  7. 【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)

    OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...

  8. 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)

    声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...

  9. iOS 运行时runtime控制私有变量以及私有方法

    OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...

随机推荐

  1. $.ajax()——超时设置

    getAjax: function (method, apiUrl, options, callback) { var xhr = $.ajax({ type: method, url: apiUrl ...

  2. hdu3472 混合欧拉

    题意:       给你一些字符串,有的字符串反过来也有意义,题目问给的这n个字符串是否可以首尾相连,组成一个串. 思路:       算是混合欧拉的基础题目了,混合欧拉就是专门处理这类问题的,先说下 ...

  3. CVE-2013-0077:Microsoft DirectShow quartz.dll m2p 文件堆溢出漏洞简单分析

    0x01 前言 2012 年 10 月 5 日,exploit-db 漏洞公布站点上发布了 QQplayer.exe 3.7.892 m2p quartz.dll Heap Pointer OverW ...

  4. c/c++ 中访问2维数组的方式

    指针是c的灵魂,这个真是不容置疑,太灵活了,太随意了, 当然,如果理解了,用得好,比弱类型语言的var 用的还舒服, 用的不好,那就是程序的灾难,哈哈,不多说了,访问二维或多维数组有如下几种方式,下面 ...

  5. 简化mapstruct代码: mapstruct-spring-plus

    mapstruct MapStruct 是一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现.MapStruct官网地址: h ...

  6. 【JavaScript】Leetcode每日一题-青蛙过河

    [JavaScript]Leetcode每日一题-青蛙过河 [题目描述] 一只青蛙想要过河. 假定河流被等分为若干个单元格,并且在每一个单元格内都有可能放有一块石子(也有可能没有). 青蛙可以跳上石子 ...

  7. Mybatis-Plus01 快速开始

    Mybatis-Plus虽然官网上有一个快速开始,但是实际操作有点问题,所以,自己写了一个. 版本说明 如果用其他软件版本可能会有部分差异 mybatis-plus:3.4.0 之前我是使用的是3.0 ...

  8. Linux x86_64与i386区别之 —— 内存寻址

    毫无疑问,不管是32位,还是64位处理器,所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是 存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不 ...

  9. BUAA-OO-第四单元总结——终章

    面向对象第四单元博客总结--终章 第四单元作业设计 第13次作业设计 类和对应方法属性设计 类设计如下图所示 本次作业主要涉及六个类,其中包括主类 Main ,通用Map类 UmlElementIdM ...

  10. CRM系统有哪几种常见类型?

    随着市场的快速变化,客户开始变得越来越重要,因此CRM客户管理系统开始逐渐被企业所认可.从CRM系统进入中国市场到现在十余年的发展中,越来越多的CRM厂商开始出现.为了满足不同行业.不同类型的企业的需 ...