【cuda】二、基础知识: 内存管理 同步

发布时间:2024年01月15日

在CUDA中,使用**cudaMalloc()来分配设备内存,使用cudaFree()**来释放设备内存。

cudaMallocManaged 统一内存管理

**统一虚拟寻址(Unified Memory):使用cudaMallocManaged()**来分配可以在CPU和GPU之间共享的内存。无需关心数据在主机或设备上。

cudaMallocManaged是一个CUDA运行时应用程序接口(API)函数,用于分配统一内存(unified memory)。统一内存是CUDA的一种内存管理模型,它提供了一个单一的、在主机和设备间共享的内存空间

这个函数的原型如下:

__host__ cudaError_t cudaMallocManaged ( void** devPtr, size_t size, unsigned int  flags = cudaMemAttachGlobal );
  • devPtr 是一个指针,指向分配的内存的地址。
  • size 是请求分配的内存的字节数。
  • flags 是一个可选的参数,用于指示内存的附着行为。默认值是 cudaMemAttachGlobal,意味着这块内存在所有的CUDA流中都是可见的。

cudaMallocManaged函数的工作原理是基于按需页面迁移"(on-demand page migration)的机制。当GPU要访问一块统一内存时,如果这块内存当前并不在GPU的物理内存中,就会触发一个页面迁移,将数据从CPU内存迁移到GPU内存。同样,当CPU要访问一块统一内存时,如果这块内存当前在GPU内存中,就会触发一个页面迁移,将数据从GPU内存迁移到CPU内存。

页面迁移可能引发的性能开销。如果主机和设备频繁地对同一块内存进行访问,可能会导致"抖动"现象,即数据不断地在主机和设备间迁移,这会大大降低程序的性能。

cudaDeviceSynchronize() &cudaStreamSynchronize()等待操作完成

cudaDeviceSynchronize()是一个CUDA运行时应用程序接口(API)函数,用于阻塞主机代码的执行,直到设备上所有先前的任务都完成为止。这包括内核执行以及设备与主机之间的内存传输。

这个函数在调试和性能测量中非常有用,因为它可以确保所有设备上的任务在继续执行主机代码之前都已完成。例如,如果你想测量GPU内核的执行时间,你需要在内核启动和停止之间插入cudaDeviceSynchronize()来确保内核完成执行。

然而,cudaDeviceSynchronize()应谨慎使用,因为它会阻塞主机代码的执行,这可能会导致性能下降。在生产代码中,通常优先使用非阻塞同步函数(如cudaStreamSynchronize()),这样可以在设备执行任务时让主机执行其他任务。

cudaMemPrefetchAsync 预迁移数据

cudaMemPrefetchAsync用于管理和优化数据在主机和设备之间的迁移,此函数可以预先迁移数据。

cudaMemPrefetchAsync的工作原理主要是基于CUDA内存管理系统和硬件的协作。当调用此函数时,CUDA运行时系统会在后台发起一个数据迁移操作,将数据从源设备迁移到目标设备。
如果目标设备是GPU,系统会将数据从主机内存复制到GPU内存。这个操作通常在CPU调用cudaMemPrefetchAsync之后立即开始,但实际的开始时间取决于系统的调度策略和设备的负载情况。一旦数据迁移到GPU,GPU上的CUDA内核就可以直接访问这些数据,而无需等待从主机内存的迁移。这可以大大减少内存访问的延迟,提高程序的性能。
如果目标设备是CPU,系统会将数据从GPU内存复制回主机内存。这个操作在GPU完成其上的所有先前操作之后才开始,以保证数据的一致性。一旦数据迁回主机,CPU就可以直接访问这些数据,无需等待从GPU内存的迁移。这对于需要在CPU上进一步处理的数据非常有用。
cudaMemPrefetchAsync函数还可以接受一个CUDA流作为参数,用于控制数据迁移的执行顺序。在同一个流中,操作按照它们被提交的顺序执行。这意味着,如果在同一个流中先后调用cudaMemPrefetchAsync和一个CUDA内核,那么CUDA运行时系统会确保数据迁移完成后才开始执行内核。这种机制提供了一种在GPU和CPU之间精细控制数据流的方式。

函数的原型如下:

cudaError_t cudaMemPrefetchAsync(const void* devPtr, size_t count, int  dstDevice, cudaStream_t stream = 0);

其中,

  • devPtr 是一个指向需要迁移的数据的指针。
  • count 是需要迁移的数据的字节数。
  • dstDevice 是目标设备。例如,可以设置为特定的GPU设备ID,或者设置为cudaCpuDeviceId表示CPU。
  • stream 是一个可选参数,表示CUDA流,用于异步操作。

通过使用cudaMemPrefetchAsync,可以控制何时以及如何将数据迁移至GPU或CPU,从而优化程序性能,减少内存访问的延迟。

CUDA Stream

在CUDA编程模型中,Stream可以被看作是设备上执行的一系列命令的队列。这些命令可以包括内核的执行、内存传输等。在同一个stream中,这些命令是按照它们入队列的顺序来执行的。而不同的stream之间则可以并发执行,这使得我们可以通过合理地使用多个stream来提高程序的并行度,从而提高程序的执行效率。

在CUDA中,我们可以通过cudaStreamCreate函数来创建一个新的stream。以下是一个简单的例子:

cudaStream_t stream;
cudaStreamCreate(&stream);

首先定义了一个cudaStream_t类型的变量stream,然后调用了cudaStreamCreate函数来创建一个新的stream,这个新创建的stream的句柄被存储在stream变量中。

我们可以在调用内核或者内存传输函数时指定stream参数,例如:

myKernel<<<gridDim, blockDim, 0, stream>>>(...);
cudaMemcpyAsync(dst, src, count, cudaMemcpyHostToDevice, stream);

在这个例子中,我们在调用myKernel内核和cudaMemcpyAsync函数时分别指定了stream参数,这意味着这两个操作将会在我们创建的stream中被执行。

在使用完stream后,我们需要调用cudaStreamDestroy函数来释放stream占用的资源,例如:

cudaStreamDestroy(stream);// 销毁流

文章来源:https://blog.csdn.net/prinTao/article/details/135610549
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。