之前测试的时候发现有时从Jenkins上拉下来的的megawise二进制包解压启动之后,执行第一条sql非常慢,需要1.5~3.5分钟不等。
后来docker化之后发现,docker中稳定必现第一条sql长时间卡住的问题。
当时查看日志发现总会卡在zdb_storage的一个MetaAgg的函数中,后来将这个函数注释掉,发现这个问题还是没有解决,会在engine中卡住。
百思不得其解之际,叶富哥一语惊醒梦中人:“好像都是卡在cuda调用的地方”。
从这个方向入手调查,果然找到了问题。
-------------------------------------揭开谜底的分割线------------------------------------------------
随着Nvidia gpu不断发展以支持新的特性,其指令集架构自然也发生了编号。由于应用程序需要在多代gpu上运行,Nvidia编译器工具链在同一个应用的可执行文件或者库中支持对多个不同架构的编译。
CUDA还依赖PTX(Parallel Thread Execution)虚拟GPU ISA(Instruction Set Architecture)来提供向前兼容性,以便已经部署的应用程序可以运行在未来的GPU架构之上。Nvidia编译器nvcc为了提供前向和
后向的兼容性,使用两段编译模型。第一个编译阶段将源代码编译成PTX虚拟汇编码,第二个阶段将PTX码编译成目标架构的二进制代码。CUDA驱动在运行时执行第二阶段编译。这种即时编译就会导致
应用程序在启动时(准确的说是在CUDA上下文创建的时候)产生一定的时间开销。CUDA使用两种方式来减少JIT(just-in-time)编译的时间开销:fat binaries和JIT缓存。
Fat Binaries
第一种完全避免即时编译带来时间开销的方法是在应用程序的二进制代码中包含面向一种或者多种架构的PTX码,CUDA运行时在二进制文件中查找当前GPU架构的代码,如果找到了直接运行。如果目标
二进制代码没有找到而PTX又是可用的,CUDA驱动会编译相应的PTX码,用这种方法,已部署的CUDA应用程序可用支持新的GPU架构。nvcc将设备码组织进fat biniaries中,这样就可以对同样的GPU源码
面向不同的设备具有灵活的翻译能力,在运行时,CUDA驱动在启动设备时会选择最合适的一种翻译。有关使用nvcc对多种架构和PTX版本生成代码的详细信息可以看这里:
https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html 。
JIT Caching
减少CUDA JIT开销的第二种方法是缓存JIT编译生成的二进制代码。当设备驱动为一个应用程序即时编译PTX代码的时候,会自动将生成的二进制代码复制一份缓存起来以避免后续应用重复调用时产生的重复
编译。当设备驱动升级时,compute cache将自动失效。控制即时编译的几个环境变量:
--------------------------------结束语分割线--------------------------------------------------
两种减少CUDA JIT时间开销的方式都比较好理解,第一种使用类似C++ 内联的方式,第二种则是利用了缓存的思想。
在docker中第一次执行必慢的原因也很好理解,docker启动后,在隔离的执行环境中是没有JIT缓存的,所以需要运行时编译。
解决方案要么将宿主机的JIT缓存映射到docker中,要么打docker时把JIT缓存打进去,当然这个缓存跟GPU架构有关,如果不知道docker用户执行环境,JIT缓存对应的目标架构不匹配也是无效的。
reference:https://devblogs.nvidia.com/cuda-pro-tip-understand-fat-binaries-jit-caching/