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. 「跬步千里」详解 Java 内存模型与原子性、可见性、有序性

    文题 "跬步千里" 主要是为了凸显这篇文章的基础性与重要性(狗头),并发编程这块的知识也确实主要围绕着 JMM 和三大性质来展开. 全文脉络如下: 1)为什么要学习并发编程? 2) ...

  2. Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...

  3. 5.PHP与Web页面交互

    PHP与Web页面交互 PHP中提供了两种与Web页面交互的方法,一种是通过Web表单提交数据,另一种是通过URL参数传递. 表单提交用户名字和密码: <form name "form ...

  4. json对象的获取

    <script type="text/javascript"> var person = { //json对象定义开始 name:'tom', //字符串 age:24 ...

  5. 【jQuery】精细学习记录

    [jQuery]精细学习记录 基础 基本语法: $(选择器).action(回调函数); $/jQuery //jQuery核心函数 $(选择器) //获得的jQuery对象 jQuery核心 - j ...

  6. .Net Core平台下,添加包的引用

    一个程序的开发过程中离不开对程序集(Assembly,将程序集打包好,就成为一个.dll的包文件,它也叫动态链接库(Dynamic Link Library​))的依赖,在以前ASP.Net时代,微软 ...

  7. Mybatis学习之自定义持久层框架(五) 自定义持久层框架:封装CRUD操作

    前言 上一篇文章我们完成了生产sqlSession的工作,与数据库的连接和创建会话的工作都已完成,今天我们可以来决定会话的内容了. 封装CRUD操作 首先我们需要创建一个SqlSession接口类,在 ...

  8. IIS部署.Net5全流程

    介绍 Internet Information Services (IIS) 是一种灵活.安全且可管理的 Web 服务器,用于托管 Web 应用(包括 ASP.NET Core).虽然我们的程序可以跨 ...

  9. Envoy :V3APi 开启 TLS

    方案架构 本次实例与官方Envoy front_proxy Example相似,首先会有一个Envoy单独运行.ingress的工作是给其他地方提供一个入口.来自外部的传入连接请求到这里,前端代理将会 ...

  10. [笔记] 《我的第一本c++书》

    函数 优秀函数的五个要点 函数的返回值:直接返回和间接返回(指针) 在函数的入口处对参数有效性进行检验:if语句,断言(assert) 如果函数有返回值,不可返回一个指向函数体内局部对象的指针或引用 ...