主要内容

GPU内存分配和最小化

离散和管理模式

GPU Coder™为您提供两种不同的内存分配(malloc)可在CUDA中使用的模式®编程模型,cudaMalloc而且cudaMallocManagedcudaMallocAPI适用于传统的CPU分离,GPU全局内存。cudaMallocManaged适用于统一内存

从程序员的角度来看,传统的计算机架构要求在CPU和GPU内存空间之间分配和共享数据。应用程序需要管理这两个内存空间之间的数据传输,这增加了复杂性。统一内存创建一个托管内存池,由CPU和GPU共享。CPU和GPU都可以通过一个指针访问托管内存。统一内存试图通过将数据迁移到需要它的设备来优化内存性能,同时对程序隐藏迁移细节。尽管统一内存简化了编程模型,但当GPU上写入的数据在CPU上被访问时,它需要设备同步调用。GPU Coder插入这些同步调用。根据NVIDIA®当使用CUDA 8.0时,统一内存可以提供显著的性能优势,或者当针对嵌入式硬件时,如NVIDIA Tegra®

若要更改GPU Coder应用程序中的内存分配模式,请使用Malloc模式下拉框更多设置->GPU编码器.在使用命令行界面时,请使用MallocMode生成配置属性并将其设置为任意一个“离散”“统一”

请注意

在未来的版本中,统一内存分配(cudaMallocManaged)模式将被移除,当目标是主机开发计算机上的NVIDIA GPU设备时。在针对NVIDIA嵌入式平台时,您可以继续使用统一内存分配模式。

GPU内存管理器

您可以使用GPU内存管理器进行高效的内存分配、管理和提高运行时性能。GPU内存管理器创建一个大型GPU内存池的集合,并在这些池中管理内存块的分配和释放。通过创建大型内存池,内存管理器减少了对CUDA内存api的调用次数,从而提高了运行时性能。您可以使用GPU内存管理器进行MEX和独立的CUDA代码生成。

要启用GPU内存管理器,请使用以下方法之一:

  • 在GPU代码配置对象(coder.gpuConfig),启用MemoryManager财产。

  • 在GPU编码器应用程序中GPU代码选项卡上,选择GPU内存管理器

  • 在Simuli金宝appnk中®配置参数对话框,代码生成> GPU代码窗格中,选择内存管理器参数。

对于使用NVIDIA CUDA库(如cuFFT, cuBLAS和cuSOLVER)的CUDA代码,您可以启用GPU内存管理器进行高效的内存分配和管理。

要在CUDA库中使用内存池,请使用上述方法之一启用内存管理器,并且:

  • 在GPU代码配置对象(coder.gpuConfig),启用EnableCUFFTEnableCUBLAS,或EnableCUSOLVER属性。

  • 在GPU编码器应用程序中GPU代码选项卡上,选择使cuFFT使cuBLAS,或使cuSOLVER

  • 在“Simul金宝appink配置参数”对话框中,代码生成> GPU代码窗格中,选择cuFFTcuBLAS,或cuSOLVER参数。

GPU内存池自定义选项

GPU内存管理器提供了表中列出的额外代码配置参数,以管理GPU内存池中内存块的分配和释放。

代码配置参数 描述 价值

在GPU代码配置对象(coder.gpuConfig):BlockAlignment

在GPU Coder应用程序中:在GPU代码选项卡,块对齐

控制块的对齐。池中的块大小(字节)是指定值的倍数。

是2的幂的正整数。缺省值为256。

在GPU代码配置对象中:FreeMode

在GPU Coder应用程序中:在GPU代码选项卡,免费模式

控制内存管理器释放GPU设备内存的时间。

当设置为“永远”,只有当内存管理器被销毁时,内存才会被释放。

使用“AtTerminate”来释放空的GPU池终止函数在生成的代码中调用。对于MEX目标,在每次调用生成的MEX函数后释放内存。对于其他目标,在调用terminate函数时释放内存。

当设置为“AfterAllocate”,每次调用CUDA内存分配后,空池被释放。

“永远”(默认)|“AtTerminate”|“AfterAllocate”

在GPU代码配置对象中:MinPoolSize

在GPU Coder应用程序中:在GPU代码选项卡,最小池大小

指定最小池大小(以兆字节为单位)。

是2的幂的正整数。缺省值为8。

在GPU代码配置对象中:MaxPoolSize

在GPU Coder应用程序中:在GPU代码选项卡,最大池大小

指定最大池大小(以MB为单位)。

内存管理器使用MinPoolSize而且MaxPoolSize参数,在两个值之间以2的幂递增进行插值。例如,如果MinPoolSize是4MaxPoolSize为1024,大小级别为{4,8,16,32,64,128,256,512,1024}。

是2的幂的正整数。缺省值为2048。

内存极小化

GPU Coder分析CPU和GPU分区之间的数据依赖关系,并进行优化,使CPU和GPU分区之间的数据依赖关系最小化cudaMemcpy生成的代码中的函数调用。分析还确定了数据必须在CPU和GPU之间复制的最小位置集cudaMemcpy

例如,函数喷火具有在CPU上按顺序处理数据和在GPU上并行处理数据的代码段。

function [out] = foo(input1,input2)…% CPU工作input1 =…input2 =…tmp1 =…tmp2 = ... ... % GPU工作内核1(gpuInput1, gpuTmp1);kernel2(gpuInput2, gpuTmp1, gpuTmp2);kernel3(gpuTmp1, gpuTmp2, gpuOut);…% CPU工作…=结束

一个未优化的CUDA实现可能有多个cudaMemcpy函数调用来传递所有输入gpuInput1, gpuInput2,和暂时的结果gpuTmp1, gpuTmp2在内核调用之间。因为中间结果gpuTmp1, gpuTmp2不使用GPU外部,它们可以存储在GPU内存内,从而减少cudaMemcpy函数调用。这些优化提高了生成代码的整体性能。优化后的实现为:

gpuInput1 = input1;gpuInput2 = input2;kernel1<<< >>>(gpuInput1, gpuTmp1);kernel2<<< >>>(gpuInput2, gpuTmp1, gpuTmp2);kernel3<<< >>>(gpuTmp1, gpuTmp2, gpuOut);out = gpuOut;

消除冗余cudaMemcpy调用,GPU Coder分析给定变量的所有使用和定义,并使用状态标志来执行最小化。这个表中显示了原始代码的示例以及生成的代码的外观。

原始代码 优化生成的代码
A(:) = ... ... for i = 1:N gB = kernel1(gA);gA = kernel2(gB);if (somcondition) gC = kernel3(gA, gB);... ... = C;
A(:) =…A_isDirtyOnCpu = true;i = 1:N if (A_isDirtyOnCpu) gA = A;A_isDirtyOnCpu = false;end gB = kernel1(gA);gA = kernel2(gB);if (somcondition) gC = kernel3(gA, gB);C_isDirtyOnGpu = true;if (C_isDirtyOnGpu) C = gC;C_isDirtyOnGpu = false;结束,结束;

_isDirtyOnCpuflag告诉GPU Coder内存优化例程,给定变量被声明并在CPU或GPU上使用。

另请参阅

应用程序

功能

对象

相关的话题