CUDA运行时 Runtime(一)
CUDA运行时 Runtime(一)
一. 概述
运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和libcudart.a,或动态通过cudart.dll或者libcudart.so. 需要cudart.dll和/或libcudart。索对于动态链接,通常将它们作为应用程序安装包的一部分包括在内。只有在链接到CUDA运行时的同一实例的组件之间传递CUDA运行时符号的地址才是安全的。
它的所有入口点都以cuda为前缀。
正如在异构编程中所提到的,CUDA编程模型假设一个系统由一个主机和一个设备组成,每个主机和设备都有各自独立的内存。设备内存概述了用于管理设备内存的运行时函数。
共享内存说明了如何使用线程层次结构中引入的共享内存来最大限度地提高性能。
页面锁定主机内存引入了页面锁定主机内存,它需要将内核执行与主机和设备内存之间的数据传输重叠起来。
异步并发执行描述了用于在系统的各个级别上启用异步并发执行的概念和API。
多设备系统显示了编程模型如何扩展到多个设备连接到同一主机的系统。
错误检查描述如何正确检查运行时生成的错误。
调用堆栈提到用于管理CUDA C++调用堆栈的运行时函数。
纹理和表面存储器提供了纹理和表面存储器空间,它们提供了访问设备存储器的另一种方式;它们还公开了GPU纹理硬件的一个子集。
图形互操作性引入了运行时提供的与两个主要图形api(OpenGL和Direct3D)互操作的各种功能。
二.初始化
运行时没有显式的初始化函数;它在第一次调用运行时函数时初始化(更具体地说,除了参考手册的错误处理和版本管理部分中的函数以外的任何函数)。在计时运行时函数调用和将错误代码从第一次调用解释到运行时时,需要记住这一点。
在初始化期间,运行时为系统中的每个设备创建一个CUDA上下文(有关CUDA上下文的更多详细信息,请参阅上下文)。此上下文是此设备的主上下文,它在应用程序的所有主机线程之间共享。作为此上下文创建的一部分,如果需要,设备代码将及时编译(请参阅及时编译)并加载到设备内存中。这一切都是透明的。如果需要,例如对于驱动程序API互操作性,可以从驱动程序API访问设备的主上下文,如运行时API和驱动程序API互操作性中所述。
当主机线程调用cudaDeviceReset()时,这会破坏主机线程当前操作的设备(即设备选择中定义的当前设备)的主上下文。任何将此设备作为当前设备的主机线程进行的下一次运行时函数调用将为此设备创建新的主上下文。
注意:CUDA接口使用全局状态,全局状态在主机程序启动时初始化,在主机程序终止时销毁。CUDA运行时和驱动程序无法检测此状态是否无效,因此在程序启动或在主程序之后终止期间使用这些接口(隐式或显式)将导致未定义的行为。
三.设备存储器
正如在异构编程中所提到的,CUDA编程模型假设一个系统由一个主机和一个设备组成,每个主机和设备都有各自独立的内存。内核在设备内存中运行,因此运行时提供分配、取消分配和复制设备内存以及在主机内存和设备内存之间传输数据的功能。
设备存储器可以作为线性存储器或CUDA阵列分配。
CUDA阵列是为纹理提取优化的不透明内存布局。它们在纹理和表面记忆中被描述。 线性存储器被分配在一个统一的地址空间中,这意味着单独分配的实体可以通过指针相互引用,例如,在二叉树或链表中。地址空间的大小取决于主机系统(CPU)和所用GPU的计算能力:
注意:在具有计算能力5.3(Maxwell)和更早版本的设备上,CUDA驱动程序创建一个未提交的40位虚拟地址存储,以确保内存分配(指针)落入支持的范围。此存储显示为存储虚拟内存,但在程序实际分配内存之前不会占用任何物理内存。
线性内存通常使用cudaMalloc()分配,使用cudaFree()释放,主机内存和设备内存之间的数据传输通常使用cudaMemcpy()完成。在内核的矢量加法代码示例中,需要将矢量从主机内存复制到设备内存:
// Device code
__global__ void VecAdd(float* A, float* B, float* C, int N)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < N) C[i] = A[i] + B[i];
}
// Host code
int main()
{
int N = ...;
size_t size = N * sizeof(float);
// Allocate input
vectors h_A and h_B in host memory
float* h_A = (float*)malloc(size);
float* h_B = (float*)malloc(size);
// Initialize input
vectors ... //
Allocate vectors in device memory
float* d_A;
cudaMalloc(&d_A, size);
float* d_B;
cudaMalloc(&d_B, size);
float* d_C;
cudaMalloc(&d_C, size); //
Copy vectors from host memory to device memory
cudaMemcpy(d_A, h_A, size,
cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, size,
cudaMemcpyHostToDevice); //
Invoke kernel
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
VecAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);
//
Copy result from device memory to host memory // h_C contains the result in host memory
cudaMemcpy(h_C, d_C, size,
cudaMemcpyDeviceToHost); //
Free device memory
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C); //
Free host memory
...
}
线性内存也可以通过cudamalocpatch()和cudamaloc3d()分配。建议将这些函数用于2D或3D数组的分配,因为它确保适当地填充分配以满足设备内存访问中描述的对齐要求,从而确保在访问行地址或在2D数组和设备内存的其他区域之间执行复制时(使用cudammcpy2d()和cudammcpy3d()函数)。返回的力度(或步幅)必须用于访问数组元素。下面的代码示例分配一个宽x高的浮点值二维数组,并演示如何在设备代码中的数组元素上循环:
// Host code
int width = 64, height = 64;
float* devPtr; size_t pitch;
cudaMallocPitch(&devPtr, &pitch,
width * sizeof(float), height);
MyKernel<<<100, 512>>>(devPtr, pitch, width, height); //
Device code
__global__ void MyKernel(float* devPtr, size_t pitch, int width, int height)
{
for (int r = 0; r < height; ++r)
{
float* row = (float*)((char*)devPtr + r * pitch);
for (int c = 0; c < width; ++c)
{
float element = row[c];
}
}
}
下面的代码示例为浮点值分配一个宽度x高度x深度的三维数组,并演示如何在设备代码中的数组元素上循环:
// Host code
int width = 64, height = 64, depth = 64;
cudaExtent extent = make_cudaExtent(width * sizeof(float), height, depth);
cudaPitchedPtr devPitchedPtr;
cudaMalloc3D(&devPitchedPtr, extent);
MyKernel<<<100, 512>>>(devPitchedPtr, width, height, depth);
// Device
code
__global__ void MyKernel(cudaPitchedPtr devPitchedPtr, int width, int height, int depth)
{
char* devPtr = devPitchedPtr.ptr;
size_t pitch = devPitchedPtr.pitch;
size_t
slicePitch = pitch * height;
for (int z = 0; z < depth; ++z)
{
char* slice = devPtr + z * slicePitch;
for (int y = 0;y < height; ++y)
{
float* row =(float*)(slice+ y * pitch);
for (int x = 0; x < width; ++x)
{
float element = row[x];
}
}
}
}
参考手册列出了用于在使用cudaMalloc()分配的线性内存、使用cudamalocpitch()或cudamaloc3d()分配的线性内存、CUDA数组和为全局或恒定内存空间中声明的变量分配的内存之间复制内存的所有各种函数。
下面的代码示例演示了通过运行时API访问全局变量的各种方法:下面的代码示例分配了一个宽x高x深的浮点值三维数组,并演示了如何在设备代码中循环数组元素:
__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(devPointer, &ptr, sizeof(ptr));
cudaGetSymbolAddress()用于检索指向为全局内存空间中声明的变量分配的内存的地址。分配的内存大小是通过cudaGetSymbolSize()获得的。
四.共享内存
如变量内存空间说明符中所述,共享内存是使用共享内存空间说明符分配的。
共享内存预计将比线程层次结构中提到的和共享内存中详细描述的全局内存快得多。它可以用作scratchpad内存(或软件管理的缓存),以最小化来自CUDA块的全局内存访问,如下面的矩阵乘法示例所示。
下面的代码示例是不利用共享内存的矩阵乘法的直接实现。每个线程读取A的一行和B的一列,并计算C的相应元素,如图9所示。因此,A从全局内存中读取B.width times,B从A.height times中读取。
// Matrices are
stored in row-major order: // M(row, col) = *(M.elements + row *M.width + col)
typedef struct
{
int width;
int height;
float*elements;
} Matrix;
// 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 = 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 = 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 = 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, Cd.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)
{
// Each thread
computes one element of C // by accumulating results into Cvalue
float Cvalue= 0;
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
for (int e = 0; e < A.width; ++e)
Cvalue += A.elements[row * A.width + e] *
B.elements[e * B.width + col];
C.elements[row * C.width + col] = Cvalue;
}
图9. 无共享内存的矩阵乘法
CUDA运行时 Runtime(一)的更多相关文章
- CUDA运行时 Runtime(四)
CUDA运行时 Runtime(四) 一. 图 图为CUDA中的工作提交提供了一种新的模型.图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义.这允许定义一次图形,然后重复启动.将 ...
- CUDA运行时 Runtime(三)
CUDA运行时 Runtime(三) 一.异步并发执行 CUDA将以下操作公开为可以彼此并发操作的独立任务: 主机计算: 设备计算: 从主机到设备的内存传输: 从设备到主机的存储器传输: 在给定设备的 ...
- CUDA运行时 Runtime(二)
CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...
- Deep Learning部署TVM Golang运行时Runtime
Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...
- iOS运行时Runtime浅析
运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...
- “ compiler-rt”运行时runtime库
" compiler-rt"运行时runtime库 编译器-rt项目包括: Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口. ...
- 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)
声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...
- iOS 运行时runtime控制私有变量以及私有方法
OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...
- 【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)
OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...
随机推荐
- 技术面试问题汇总第002篇:猎豹移动反病毒工程师part2
这次拿三个问题来讨论,是关于调试器的.因为对于反病毒工程师而言,类似于OllyDbg和IDA的使用方法是必须掌握的,但是在面试中又不太方便考察,所以只能对其快捷键或者调试器实现原理之类的问题进行提问. ...
- PyCharm调试程序
当我们在运行python程序出错时,我们需要定位到出错的位置.有时候通过程序运行时的报错可以很容易的找到出错的位置,但是有时候必须得通过调试程序才能找出我们的错误. PyCharm中要调试程序的话,在 ...
- HTTP协议之分块传输与分段编码
目录 数据的分块传输 数据的分段编码(transfer-encoding) 前置知识:HTTP协议 数据的分块传输 我们都知道http协议是由TCP协议封装而来的应用层协议.我们和服务器之间的每次ht ...
- Windows核心编程 第2 4章 异常处理程序和软件异常
异常处理程序和软件异常 C P U引发的异常,就是所谓的硬件异常(hardware exception).操作系统和应用程序 也可以引发相应的异常,称为软件异常(software exception) ...
- windows-DLL注入
DLL注入 刚刚整理的代码注入(远程线程注入)现在整理这个DLL注入,DLL注入比较常用,相比远程代码注入DLL注入没有什么太多的限制,而且实现起来比较简单,当然远程线程需要注意的问题DLL很 ...
- 通过 Netty、ZooKeeper 手撸一个 RPC 服务
说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...
- Linux下为Calibre书库打中文目录名与文件名补丁
本文由来 临近下班突然看到知乎上有篇文章是给Calibre打中文目录与文件名补丁的,想起我之前为啥放弃Calibre的--存进书库里书的名称都变成了拼音!手动找起来或者搜索工具找起来太麻烦了(有时想不 ...
- shell 脚本中常用的内置变量
在 Bash 解释器中,内置了许多变量,这些变量的功能是解释器自带的,我们在编写shell脚本时如果能灵活的使用它们,对脚本的编写效率以及差错大有帮助, 下面一一介绍这些变量 $FUNCNAME.$L ...
- JAVA中Abstract到底有什么用?都用在哪些方面比较合适?
功能向上聚合 Abstract作为抽象类和抽象方法,第一种情况是在聚合子类的通用性上起到作用,往往出现在重构过程中自然而然形成的一种层次结构-希望将多个子类的通用方法和逻辑提取到父层的抽象类. 这种重 ...
- XAML常用控件2
这节继续讲一些xaml中的常用控件. 布局控件 除了我们之前讲过的Grid,StackPanel,Border布局控件,xaml中还有如下几个布局控件: Canvas:使用这个布局,可以通过坐标来控制 ...