Linux C++程序使用Intel OpenCL的问题
Intel OpenCL 问题
最近想在 Linux 环境下使用 Intel 核显测试我们的某个程序,该程序是用 C++ 写的,主要调用 OpenCL 库使用 GPU 进行运算。之前我用 AMD 和 NVIDIA 的显卡都测试过没有问题,还以为会比较顺利,结果在程序刚开始调用 clGetPlatformIDs
检测 OpenCL platform 的时候就直接报错退出了。
我的测试环境使用的是 Intel i3-6100 CPU,该处理器自带 Intel HD Graphics 530 核显,操作系统则是 CentOS 6.9 64 位(比较老,为了和使用环境一致),安装的也是 Intel 官方网站上提供的 Intel OpenCL 2.0 Driver(SRB 5.0 版本),具体可以参考 Intel 官网的 OpenCL™ Drivers and Runtimes for Intel® Architecture 介绍。
值得一提的是 Intel OpenCL Driver 安装完成之后,运行 clinfo
命令是可以正常识别到核显的,而且我使用另外一个通过 OpenCL 进行计算的 C 程序来测试又是完全没问题的。
调试分析
初步考虑可能 Intel OpenCL 在此系统和 C++ 程序存在兼容性问题,为此我先写了一个最简单的 C++ 程序来检测 OpenCL platform:
#include <iostream> #include <string> #include <CL/cl.h> void init_cl() { cl_uint plats = 0; std::cout << "Begin get OpenCL platform." << std::endl; clGetPlatformIDs(0, NULL, &plats); std::cout << "OpenCL platforms: " << plats << std::endl; } int main(int argc, char **argv) { std::string str = "cl"; init_cl(); return 0; }
使用 g++
编译该测试程序,编译时启用优化:
[root@localhost ~]# g++ -O2 -o cl cl.cpp -lOpenCL -lstdc++
测试程序使用 AMD 和 NVIDIA 显卡的 OpenCL 库都可以正常检测到 platform,使用 Intel 核显则会遇到下面的错误:
[root@localhost tmp]# ./cl Begin get OpenCL platform. *** glibc detected *** ./cl: free(): invalid pointer: 0x0000003de60f32c0 *** ======= Backtrace: ========= /lib64/libc.so.6[0x328cc75716] /usr/lib64/libstdc++.so.6(_ZNSs6assignERKSs+0x85)[0x3de5e9d565] /opt/intel/opencl/libigdmcl.so(+0x2040ba)[0x7f75360a00ba] /opt/intel/opencl/libigdmcl.so(+0x1e6cc2)[0x7f7536082cc2] /lib64/ld-linux-x86-64.so.2[0x328c40e4ef] /lib64/ld-linux-x86-64.so.2[0x328c412bf2] /lib64/ld-linux-x86-64.so.2[0x328c40e106] /lib64/ld-linux-x86-64.so.2[0x328c41245a] /lib64/libdl.so.2[0x328c800f66] /lib64/ld-linux-x86-64.so.2[0x328c40e106] /lib64/libdl.so.2[0x328c80129c] /lib64/libdl.so.2(dlopen+0x31)[0x328c800ee1] /opt/intel/opencl/libigdrcl.so(+0x124bd3)[0x7f7538082bd3] /opt/intel/opencl/libigdrcl.so(+0xf4890)[0x7f7538052890] /opt/intel/opencl/libigdrcl.so(+0xf4e26)[0x7f7538052e26] /opt/intel/opencl/libigdrcl.so(+0xe8982)[0x7f7538046982] /opt/intel/opencl/libigdrcl.so(+0xd0f3c)[0x7f753802ef3c] /opt/intel/opencl/libigdrcl.so(clIcdGetPlatformIDsKHR+0x2b)[0x7f753801a0ab] /opt/intel/opencl/libIntelOpenCL.so(+0x234e4)[0x7f75388c34e4] /opt/intel/opencl/libIntelOpenCL.so(+0x3f20)[0x7f75388a3f20] /usr/lib64/libOpenCL.so.1(+0x298b)[0x7f7538f0b98b] /usr/lib64/libOpenCL.so.1(+0x4907)[0x7f7538f0d907] /lib64/libpthread.so.0(pthread_once+0x53)[0x7f7538adfe03] /usr/lib64/libOpenCL.so.1(clGetPlatformIDs+0x11)[0x7f7538f0bf21] ./cl[0x400c6e] ./cl[0x400d31] /lib64/libc.so.6(__libc_start_main+0xfd)[0x328cc1ec9d] ./cl[0x400b09] ======= Memory map: ======== 00400000-00402000 r-xp 00000000 00:11 10291 /tmp/cl 00601000-00602000 rw-p 00001000 00:11 10291 /tmp/cl 0179d000-017be000 rw-p 00000000 00:00 0 [heap] 328c400000-328c420000 r-xp 00000000 fd:00 7124 /lib64/ld-2.12.so 328c61f000-328c620000 r--p 0001f000 fd:00 7124 /lib64/ld-2.12.so 328c620000-328c621000 rw-p 00020000 fd:00 7124 /lib64/ld-2.12.so 328c621000-328c622000 rw-p 00000000 00:00 0 328c800000-328c802000 r-xp 00000000 fd:00 7116 /lib64/libdl-2.12.so 328c802000-328ca02000 ---p 00002000 fd:00 7116 /lib64/libdl-2.12.so 328ca02000-328ca03000 r--p 00002000 fd:00 7116 /lib64/libdl-2.12.so 328ca03000-328ca04000 rw-p 00003000 fd:00 7116 /lib64/libdl-2.12.so 328cc00000-328cd87000 r-xp 00000000 fd:00 6933 /lib64/libc-2.12.so 328cd87000-328cf87000 ---p 00187000 fd:00 6933 /lib64/libc-2.12.so 328cf87000-328cf8b000 r--p 00187000 fd:00 6933 /lib64/libc-2.12.so 328cf8b000-328cf8c000 rw-p 0018b000 fd:00 6933 /lib64/libc-2.12.so 328cf8c000-328cf91000 rw-p 00000000 00:00 0 328d400000-328d483000 r-xp 00000000 fd:00 6818 /lib64/libm-2.12.so 328d483000-328d682000 ---p 00083000 fd:00 6818 /lib64/libm-2.12.so 328d682000-328d683000 r--p 00082000 fd:00 6818 /lib64/libm-2.12.so 328d683000-328d684000 rw-p 00083000 fd:00 6818 /lib64/libm-2.12.so 328d800000-328d807000 r-xp 00000000 fd:00 7077 /lib64/librt-2.12.so 328d807000-328da06000 ---p 00007000 fd:00 7077 /lib64/librt-2.12.so 328da06000-328da07000 r--p 00006000 fd:00 7077 /lib64/librt-2.12.so 328da07000-328da08000 rw-p 00007000 fd:00 7077 /lib64/librt-2.12.so 3de5e00000-3de5ee8000 r-xp 00000000 00:11 9623 /usr/lib64/libstdc++.so.6.0.13 3de5ee8000-3de60e8000 ---p 000e8000 00:11 9623 /usr/lib64/libstdc++.so.6.0.13 3de60e8000-3de60ef000 r--p 000e8000 00:11 9623 /usr/lib64/libstdc++.so.6.0.13 3de60ef000-3de60f1000 rw-p 000ef000 00:11 9623 /usr/lib64/libstdc++.so.6.0.13 3de60f1000-3de6106000 rw-p 00000000 00:00 0 7f7535c86000-7f7535c9b000 r-xp 00000000 fd:00 7143 /lib64/libz.so.1.2.3 7f7535c9b000-7f7535e9a000 ---p 00015000 fd:00 7143 /lib64/libz.so.1.2.3 7f7535e9a000-7f7535e9b000 r--p 00014000 fd:00 7143 /lib64/libz.so.1.2.3 7f7535e9b000-7f7535e9c000 rw-p 00015000 fd:00 7143 /lib64/libz.so.1.2.3 7f7535e9c000-7f7536b3a000 r-xp 00000000 07:05 10 /opt/intel/opencl/libigdmcl.so 7f7536b3a000-7f7536d3a000 ---p 00c9e000 07:05 10 /opt/intel/opencl/libigdmcl.so 7f7536d3a000-7f7536d91000 r--p 00c9e000 07:05 10 /opt/intel/opencl/libigdmcl.so 7f7536d91000-7f7536d93000 rw-p 00cf5000 07:05 10 /opt/intel/opencl/libigdmcl.so 7f7536d93000-7f7536d9f000 rw-p 00000000 00:00 0 7f7536d9f000-7f7536da7000 r-xp 00000000 fd:00 13144 /usr/lib64/libpciaccess.so.0.11.1 7f7536da7000-7f7536fa7000 ---p 00008000 fd:00 13144 /usr/lib64/libpciaccess.so.0.11.1 7f7536fa7000-7f7536fa8000 rw-p 00008000 fd:00 13144 /usr/lib64/libpciaccess.so.0.11.1 7f7536fa8000-7f7537351000 r-xp 00000000 07:05 12 /opt/intel/opencl/libmd.so 7f7537351000-7f7537550000 ---p 003a9000 07:05 12 /opt/intel/opencl/libmd.so 7f7537550000-7f7537554000 r--p 003a8000 07:05 12 /opt/intel/opencl/libmd.so 7f7537554000-7f7537556000 rw-p 003ac000 07:05 12 /opt/intel/opencl/libmd.so 7f7537556000-7f753755d000 rw-p 00000000 00:00 0 7f753755d000-7f753755e000 ---p 00000000 00:00 0 7f753755e000-7f7537f5e000 rw-p 00000000 00:00 0 7f7537f5e000-7f7538152000 r-xp 00000000 07:05 11 /opt/intel/opencl/libigdrcl.so 7f7538152000-7f7538351000 ---p 001f4000 07:05 11 /opt/intel/opencl/libigdrcl.so 7f7538351000-7f7538357000 r--p 001f3000 07:05 11 /opt/intel/opencl/libigdrcl.so 7f7538357000-7f7538894000 rw-p 001f9000 07:05 11 /opt/intel/opencl/libigdrcl.so 7f7538894000-7f75388a0000 rw-p 00000000 00:00 0 7f75388a0000-7f75388cb000 r-xp 00000000 07:05 2 /opt/intel/opencl/libIntelOpenCL.so 7f75388cb000-7f7538acb000 ---p 0002b000 07:05 2 /opt/intel/opencl/libIntelOpenCL.so 7f7538acb000-7f7538acc000 r--p 0002b000 07:05 2 /opt/intel/opencl/libIntelOpenCL.so 7f7538acc000-7f7538acd000 rw-p 0002c000 07:05 2 /opt/intel/opencl/libIntelOpenCL.so 7f7538acd000-7f7538ad3000 rw-p 00000000 00:00 0 7f7538ad3000-7f7538aea000 r-xp 00000000 fd:00 7139 /lib64/libpthread-2.12.so 7f7538aea000-7f7538cea000 ---p 00017000 fd:00 7139 /lib64/libpthread-2.12.so 7f7538cea000-7f7538ceb000 r--p 00017000 fd:00 7139 /lib64/libpthread-2.12.so 7f7538ceb000-7f7538cec000 rw-p 00018000 fd:00 7139 /lib64/libpthread-2.12.so 7f7538cec000-7f7538cf2000 rw-p 00000000 00:00 0 7f7538cf2000-7f7538d08000 r-xp 00000000 fd:00 7140 /lib64/libgcc_s-4.4.7-20120601.so.1 7f7538d08000-7f7538f07000 ---p 00016000 fd:00 7140 /lib64/libgcc_s-4.4.7-20120601.so.1 7f7538f07000-7f7538f08000 rw-p 00015000 fd:00 7140 /lib64/libgcc_s-4.4.7-20120601.so.1 7f7538f08000-7f7538f09000 rw-p 00000000 00:00 0 7f7538f09000-7f7538f10000 r-xp 00000000 fd:00 12875 /usr/lib64/libOpenCL.so.1 7f7538f10000-7f753910f000 ---p 00007000 fd:00 12875 /usr/lib64/libOpenCL.so.1 7f753910f000-7f7539110000 rw-p 00006000 fd:00 12875 /usr/lib64/libOpenCL.so.1 7f7539110000-7f7539111000 rw-p 00000000 00:00 0 7ffe17048000-7ffe17069000 rw-p 00000000 00:00 0 [stack] 7ffe170de000-7ffe170e1000 r--p 00000000 00:00 0 [vvar] 7ffe170e1000-7ffe170e3000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Aborted
从错误输出来看是 clGetPlatformIDs
调用过程中 Intel OpenCL Driver 内部就出现问题了,释放了无效的指针导致程序异常退出。
几番修改测试之后我发现两个现象:
- 如果
g++
编译时不使用优化选项(-O2
参数去掉或者改为-O0
),则程序可以正常运行; - 如果去掉第 15 行的
std::string
变量定义代码,即使保持优化选项开启并链接 libstdc++ 库,程序也可以正常运行。
看起来只要 libstdc++ 库确实被使用了,优化过的 C++ 程序调用 Intel OpenCL 就会出现异常,而由于我们的程序需要开启编译器优化,因此要找到其它解决办法。
解决方法
局部关闭编译器优化
我们可以在整个程序开启编译器优化的情况下,只关闭对调用 OpenCL 库那部分代码的优化。下面是两种局部关闭编译器优化的方法,通过这两种方法修改的测试程序编译之后都可以正常运行。
pragma 指令
把测试程序的代码改成这样:
#pragma GCC push_options #pragma GCC optimize ("O0") void init_cl() { ... } #pragma GCC pop_options
使用 pragma 指令指定 #pragma GCC push_options
和 #pragma GCC pop_options
包起来的这部分代码的编译选项,好处是可以包含多个函数。
__attribute__ 属性
我们也可以直接通过 GNU 编译器的 __attribute__
编译器属性指定某个函数的优化选项:
void __attribute__((optimize("O0"))) init_cl()
升级 libstdc++ 库
不过有点悲剧的是我们的 C++ 程序通过上面两种方法关闭 OpenCL 调用部分代码的编译器优化之后,运行之后仍然异常退出。另外我把程序挪到 CentOS 7.1 64 位系统上运行则没有任何问题,最终考虑要通过升级 libstdc++ 库(CentOS 6.9 上的是 4.4.7-18 版本)来解决此问题。
本来打算通过手工编译新版本 GCC 来生成新版本的 libstdc++ 库,不过还好搜索之后发现网上已经有现成的 CentOS 6 上的新版本 libstdc++ 了,具体可以参考 SAP 公司提供的 compat-sap-c++ for RedHat 6.7+。
按照 SAP 网站上的说明安装 compat-sap-c++
包,我们也可以直接替换系统链接:
[root@localhost ~]# curl -O ftp://ftp.pbone.net/mirror/ftp.scientificlinux.org/linux/scientific/6.7/x86_64/updates/fastbugs/compat-sap-c++-4.8.2-16.el6.x86_64.rpm [root@localhost ~]# rpm -ivh compat-sap-c++-4.8.2-16.el6.x86_64.rpm [root@localhost ~]# ln -sfn /opt/rh/SAP/lib64/compat-sap-c++.so /usr/lib64/libstdc++.so.6
替换 libstdc++ 为 4.8.2-16 版本之后再运行 OpenCL 测试程序就会发现即使开启编译器优化也没有问题了:
[root@localhost tmp]# ./cl Begin get OpenCL platform. OpenCL platforms: 1
看来 Intel 官网上介绍的 Intel OpenCL Driver 只验证了 CentOS 7.2、7.3 及 Ubuntu 16.04 系统下的兼容性还算靠谱的,如果要在老一点的 Linux 系统上使用 Intel OpenCL 还是需要尽量避免碰坑,最后祝大家玩的开心。