一、延迟加载
Lazy Loading,延迟加载,也有的翻译成惰性加载。其实只要经验稍微丰富一些的开发人员或者有过模板开发经验的C++程序员,对延迟加载并不陌生。
所以谓延迟加载,其实很好理解,就是在需要时再加载。这是什么意思呢?大家学习的开发流程,一般是在程序初始化时,就会将所有的资源都加载进来,这样的好处显而易见。随取随用,方便快捷。但它也有一个问题,比如某一个功能应用的情况非常少,而它需要加载的资源反而特别多。甚至有可能程序运行好久也不见的能用上一次。这时,大量的资源被闲置。
而在延迟加载中,对于上述的情况,可以改为在需要调用这个功能时,才将相关的资源加载进来,从而显式的将资源的利用进行了优化。当然,它也有它的缺点,就是延迟加载可能导致第一次调用的效率不高而且有可能出现异常导致问题的后置解决。当然,延迟加载也增加了资源管理的复杂性和调试分析的复杂性。
还是那句话,没有任何一项技术能包打天下。关键是要用得好,用得合适,用得恰到好处。
二、CUDA的Lazy Loading
既然延迟加载有它的优势,那么对于CUDA框架来说,当然要加强它的优势应用场景而避免它的劣势应用场景,扬其长而去其短。
在CUDA中,延迟加载可以将其模块和内核的加载时间从程序初始化阶段推迟到更趋近于内核执行的时间。即当程序只包含自己启动所需要的内核,而不需要的内核则不被加载。特别是在调用一个非常大的库时,如果只使用了其中的一个小功能,这样做的话,能带来非常大的好处,比如启动速度的明显加快。
另外,延迟加载还带来一个更好之处那就是明显的降低了GPU和主机内存的开销,这在某些场景下的优势不言而喻。
不过延迟加载对CUDA版本和驱动版本的要求都比较高,需要在CUDA11.7后才能正常应用,同时需要R515+用户模式库。
三、延迟加载的方法和问题
在CUDA框架中,可以将环境变量CUDA_MODULE_LOADING设置为LAZY来启用延迟加载。
首先,CUDA运行时将不会在程序初始化期间加载所有模块(包含托管变量的模块除外)。它们将在第一次使用该模块中的变量或内核时被加载(此优化仅与CUDA运行时用户相关,使用cuModuleLoad的CUDA驱动程序用户不受影响)。对于使用cuLibraryLoad将模块数据加载到内存中的CUDA驱动程序用户,其行为可以通过设置CUDA_MODULE_DATA_LOADING环境变量来更改。
其次,加载模块(cuModuleLoad * () 系列函数)将不会立即加载内核,而是会延迟到调用 cuModuleGetFunction() 时才加载内核(如果有些内核必须在cuModuleLoad*() 期间加载,例如指针存储在全局变量中的内核,则不包括在内)。此优化与CUDA运行时用户和CUDA驱动程序用户都相关。CUDA运行时只会在内核首次被使用/引用时调用cuModuleGetFunction()。
需要说明的是,在遵循CUDA编程模型的情况下,这两项优化在设计上对用户是透明的。
当然,使用CUDA的延迟加载存在着一些问题需要开发者注意,包括:
- 并行运行可能的死锁问题
如果两个任务进行同步,一个任务需要另外一个任务的操作结果,如果被需要的任务进行了延迟加载,结果是显而易见,必然会产生死锁。
处理的方法有两种,一种是提前预加载相关的任务;另外一个是使用CUDA_MODULE_DATA_LOADING=EAGER来强制加载所有数据。 - 内存分配
延迟加载可能导致内存分配的异常,比如程序在启动时没有问题,但如果调用延迟加载的函数时,需要分配超出现有内存大小的内存时,就会出现异常。
解决方法的也比较简单,一个是预加载所有的内存分配功能;二是调用cudaMallocAsync()来进行内存分配;三是使用内存池或缓冲技术。 - 自动调优
在某些应用场景下,可能会一次启动多个功能相同的内核,来确定哪个最快。那么此时的延迟加载受到影响就比较大了。毕竟将加载的时间计入测量会产生异常的时间测量结果。
处理的方法有两种,一种是测量前至少预热一次;另外一个是在基准测试前进行预加载
四、例程
可以使用下面的代码进行延迟加载的相关查询:
#include "cuda.h"
#include "assert.h"
#include "iostream"
int main() {
CUmoduleLoadingMode mode;
assert(CUDA_SUCCESS == cuInit(0));
assert(CUDA_SUCCESS == cuModuleGetLoadingMode(&mode));
std::cout << "CUDA Module Loading Mode is " << ((mode == CU_MODULE_LAZY_LOADING) ? "lazy" : "eager") << std::endl;
return 0;
}
需要说明的是,调用cuModuleGetLoadingMode函数前,CUDA必须已经完成了初始化。
五、总结
其实大家看着看着就发现,其实所有的语言和框架其实都是相通的。只不过具体的实现的模式和角度不同罢了。就象这个延迟加载,几乎大部分的高级语言都会支持,如果应用的好,对于提高性能和资源管理的优化是大有禆益的。

252






