想用SVML
在正常情况下,.so文件作为Runtime隐藏起来,程序员只要面向头文件编程即可。例如多线程库pthread在使用时只需要包含头文件pthread.h
,在链接时加上-lpthread
选项就可以。
但是,在某些情况下,能够获得的只有.so文件,而又没有类似的替代物,那也只能硬着头皮上了。具体到笔者的例子,是intel的svml库。
svml库是intel的向量指令库。它与一堆的SSE、AVX、MMX同样,属于SIMD指令的一部分。比较特别的是,它包含了三角函数、指数函数、双曲三角函数等指令。相比其他SIMD系列,它的intrinsic不对外授权,因此gcc自带了SSE、AVX等等,却不自带svml。
想用svml,那就下intel C++ compiler去吧。学生邮箱倒是可以免费,但是国内的学校好像不认;也可以作为开源开发者申请,但是要走审批流程,2个工作日起。
经过一番搜索,发现对外开放的intel OpenCL Runtime中包含了svml的二进制代码。在Ubuntu 18.04上下载并安装opencl_runtime_16.1.2_x64)rh_6.4.0.37.tgz
中的内容后,在/opt/intel/opencl/lib64
下将存在__ocl_svml_e9.so
、__ocl_svml_h8.so
和__ocl_svml_l9.so
三个.so文件。我们想用的函数就在其中实现,只不过是机器码。
确定.so文件中的内容
使用nm
、objdump
和readelf
都可以查看.so文件中的内容。例如
-
$ nm -D __ocl_svml_h8.so
的结果
-
$ objdump -T __ocl_svml_h8.so
的结果
-
$ readelf -s __ocl_svml_h8.so
的结果
值得一提的是,使用objdump可以反编译这个.so文件。执行$ objdump -D __ocl_svml_h8.so > output.asm
可以看到反编译的结果。
我想用的函数就是图中的__ocl_svml_h8_sinf8
,也就是使用256bit的大宽度SIMD寄存器一次计算8个32位浮点数的sin值。
通过反汇编,可以发现其底层实现并不是一条指令,而是一堆指令的集合。换句话说,其实是官方写了一个库。由于有h8,e9,l9三个文件,因此把它们的反汇编都看了一遍,发现实现都非常长,因此基本确定sinf8是软件实现。
真是失望啊,回头还是用查表法解决问题吧。不过事情还得继续,.so还是得用起来。
怎么使用.so文件
思路很简单。造一个头文件。svml的用法在intel intrinsics guide中有提到。
很直接的思路是直接在头文件中写入一行:__m256 _mm256_sin_ps(__256 a);
。但是显然不行。.so文件中的函数是通过符号名来检索的,我们需要在头文件中生成一个符号名与.so文件相同的函数定义。
C的name mangling
name mangling是指C/C++在编译过程中,将函数名转为符号名的一套规则。不同的编译器通常不同。而在我们的.so文件中出现的符号名明显和C不是一个画风。
因此,通过符号名反推函数名是不行的。我们需要的是钦定任意一个函数的符号名的办法。而gcc提供有这样的功能。下面这行代码就声明了一个符号名为“functiona”,函数名为func的函数:
void func() __asm__("functiona");
最后解决
于是,操作就是很简单了。
我们写了个测试文件:
#include
#include
__m256 __ocl_svml_e9_sinf8(__m256 ) __asm__("__ocl_svml_e9_sinf8");
int main(){
__m256 a, b;
a = _mm256_set1_ps(10.0);
b = __ocl_svml_e9_sinf8(a);
float* data = (float*) aligned_alloc(64, 256/8);
_mm256_store_ps(data, b);
printf("%f\n", data[0]);
}
编译使用的命令是:
$ gcc -mavx test.cpp __ocl_svml_e9.so
编译生成了a.out
文件,运行后报错,显示找不到.so文件。只要将报错信息显示的so文件拷贝成需要的名字,放到/usr/lib或者其他合适的位置即可。