opencl初始化速度提升:kernel编译和clFFT初始化

 

opencl的kernel脚本一般在应用程序运行时调用clCreateProgramWithSource、clBuildProgram进行编译,若脚本中程序较多,则编译时消耗一定的时间。

在桌面级GPU中,这个时间消耗很小,一般在10~30ms,但在嵌入式GPGPU中,编译时间会很长,达到10秒以上,严重影响系统的初始化时间。

opencl提供了clCreateProgramWithBinary接口,用于减少初始化时间。

使用时,首先通过clGetProgramInfo函数将已经编译好的程序保存下来,下次启动时,调用clCreateProgramWithBinary加载,即可大幅降低编译时间。

 1 int save_bin(const char *filename) //在已经完成编译的条件下,保存成文件
 2 {
 3     size_t binary_size = 0;
 4     clGetProgramInfo( program_, CL_PROGRAM_BINARY_SIZES, //获得本程序的大小
 5             sizeof(size_t), &binary_size, NULL);
 6 
 7     vector<u8> program_binary(binary_size);
 8     u8* binary_ptrs[] = { program_binary.data() };
 9     clGetProgramInfo( program_, CL_PROGRAM_BINARIES,
10             sizeof(program_binary.data()), binary_ptrs, NULL);
11 
12     ofstream ofs(filename, ios::out | ios::binary);
13     if (!ofs.is_open())
14     {
15         return 1;
16     }
17 
18     ofs.write((const char*)program_binary.data(), binary_size);
19     ofs.close();
20     return 0;
21 }
22 int load_bin(const char *filename) //从二进制文件读取程序,失败返回非0
23 {
24     CComFile file;
25     file.open(filename, "rb");
26     vector<u8> buf(file.len);
27     file.read(buf.data(), file.len);
28     file.close();
29 
30     cl_int err= 0;
31     cl_int status= 0;
32     cl_device_id dev_ids[1] = {device->id};
33     size_t len=buf.size();
34     const u8* binary_ptrs[] = { buf.data() };
35     program_ = clCreateProgramWithBinary(device->ctx, 1, dev_ids, &len, binary_ptrs, &status, &err);
36     if (program_ == nullptr) { return 1; }
37 //编译Program对象
38     const char* opt_str=" -cl-mad-enable";
39     //DBGL;
40     err = clBuildProgram(program_, 1, dev_ids, opt_str, NULL, NULL);
41     //DBGL;
42     if (err != CL_SUCCESS)
43     {
44         char build_log[KB(16)]={0};
45         clGetProgramBuildInfo(program_, dev_ids[0], CL_PROGRAM_BUILD_LOG, KB(16), build_log, NULL);
46         THROW<<sFormat("fail to build program, ret=%d %s", err, build_log).c_str();
47     }
48     return 0;
49 }

下次程序启动时,首先判断是否有保存的bin文件,若有,则加载,若没有则编译,并存储为bin文件。
保存的文件其实也不是二进制文件,而是文本指令:

 1 //
 2 // Generated by NVIDIA NVVM Compiler
 3 //
 4 // Compiler Build ID: UNKNOWN
 5 // Unknown Toolkit Version
 6 // Based on NVVM 7.0.1
 7 //
 8 
 9 .version 8.3
10 .target sm_86, texmode_independent
11 .address_size 64
12 
13     // .globl    s16_2_float
14 // transpose_$_tile has been demoted
15 // sar_1_R_$_tile has been demoted
16 .global .align 4 .b8 __cudart_i2opi_f[24] = {65, 144, 67, 60, 153, 149, 98, 219, 192, 221, 52, 245, 209, 87, 39, 252, 41, 21, 68, 78, 110, 131, 249, 162};
17 
18 .entry s16_2_float(
19     .param .u32 s16_2_float_param_0,
20     .param .u64 .ptr .global .align 2 s16_2_float_param_1,
21     .param .u64 .ptr .global .align 4 s16_2_float_param_2
22 )
23 {
24     .reg .b16     %rs<3>;
25     .reg .f32     %f<3>;
26     .reg .b32     %r<15>;
27     .reg .b64     %rd<12>;
28 
29 
30     ld.param.u32     %rd1, [s16_2_float_param_0];
31     ld.param.u64     %rd2, [s16_2_float_param_1];
32     ld.param.u64     %rd3, [s16_2_float_param_2];
33     mov.b32     %r1, %envreg3;
34     mov.u32     %r2, %ntid.x;
35     mov.u32     %r3, %ctaid.x;
36     mov.u32     %r4, %tid.x;
37     add.s32     %r5, %r4, %r1;
38     mad.lo.s32     %r6, %r2, %r3, %r5;
39     cvt.s64.s32     %rd4, %r6;
40     mov.b32     %r7, %envreg4;
41     mov.u32     %r8, %ntid.y;
42     mov.u32     %r9, %ctaid.y;
43     mov.u32     %r10, %tid.y;
44     add.s32     %r11, %r10, %r7;
45     mad.lo.s32     %r12, %r8, %r9, %r11;
46     mov.b32     %r13, %envreg6;
47     mul.lo.s32     %r14, %r13, %r2;
48     mul.wide.s32     %rd5, %r12, %r14;
49     add.s64     %rd6, %rd5, %rd4;
50     add.s64     %rd7, %rd6, %rd1;
51     shl.b64     %rd8, %rd7, 2;
52     add.s64     %rd9, %rd2, %rd8;
53     ld.global.u16     %rs1, [%rd9];
54     ld.global.u16     %rs2, [%rd9+2];
55     // begin inline asm
56     cvt.rn.f32.s16     %f1, %rs1;
57     // end inline asm
58     // begin inline asm
59     cvt.rn.f32.s16     %f2, %rs2;
60     // end inline asm
61     shl.b64     %rd10, %rd6, 3;
62     add.s64     %rd11, %rd3, %rd10;
63     st.global.f32     [%rd11], %f1;
64     st.global.f32     [%rd11+4], %f2;
65     ret;
66 
67 }

经过优化后,在英伟达RTX 3060上测试,发现对于大约700行的kernel,编译时间由20~30ms,缩短到了10~20ms,比较随机。
但在某国产化GPGPU-SOC上测试时,发现clBuildProgram函数接受的有效代码(除注释)有限,大约500行就不行了(不报错,程序直接退出,显示Killed),代码数量越多,编译速度越慢。应该是有什么东西限制。如果一个程序太大,需要分多个部分编译
编译时间12s左右,直接加载上次编译好的bin文件,时间降低到了0.7秒左右,效果明显。

对于clFFT的初始化,也采用相同的办法,只是clFFT的初始化内部带有cache机制,只需设置环境变量使能cache即可。
clFFT的初始化主要是Bake过程,如果不bake,直接使用plan,首次执行时间就会很长,总时间与先bake后执行一致。

在某国产化GPGPU-SOC上测试clFFT的初始化速度,设置环境变量:export CLFFT_CACHE_PATH=.
让clFFT初始化bake的时候,将编译结果缓存至指定目录。下次启动时,先从缓存目录读取文件,若没有文件,才进行编译。
clFFT的启动速度由原来的4秒左右,降低到了3.8秒左右,测试了4组不同的FFT配置,都是缩短200ms。所以clFFT的启动速度没有明显提升。将clFFT的初始化放在辅线程中执行,结果最后一个plan的初始化与第一个plan的计算发生了并行,导致两者执行速度都变慢了,总时间与串行一致

 

posted on 2024-06-28 22:43  yangzifb  阅读(4)  评论(0编辑  收藏  举报