页次: 1
啊因为博客关了好久了,昨天折腾了一下 Intel® mkl,感觉过程还是蛮有意思的,想记录下来又没地方写,想想不如写到社区,还可以和大家分享一下。
0. 前言
由于 octave 的运算速度实在是太慢了,和 matlab 相比相差甚远,所以想改进一下!正好差两天毕业,趁着还有 edu 邮箱,所以申请了个 parallel_studio_xe_2016 来体验一发,当然主要用到了里面的 mkl。本来还打算用 icc 来编译一个 octave 的,但是苦于连 ./configure 阶段都没法通过,只好作罢……
1. 简介
octave 依赖 lapack blas fftw 三个包进行线性代数运算和傅立叶变换。
lapack 和 blas 默认是单线程的,无法忍受啊!
archlinuxcn 源(以及 AUR)提供了 openblas,多线程,速度要比 lapack 和 blas 快很多了!
community 源提供了 atlas-lapack-base,这是一个适配大多数处理器的 atlas lapack,多线程,相比 openblas 性能差了一些。
由于 atlas-lapck-base 是适配于大多数处理器的包,所以并不能发挥出 atlas lapack 的最佳性能。所以 AUR 提供了 atlas-lapack 包,这个包需要在本地编译,编译过程中会指定处理器个数,编译好之后测试了一下,相比 atlas-lapack-base 性能提示还是蛮大的,比 openblas 稍好一点。
fftw 包默认是多线程,但是后期测试性能还是不如 Intel® mkl 啊。
2. 过程
首先是下载了最新的 parallel_studio_xe_2016, 这玩意 3.9GiB 真够大的!然后通过 edu 邮箱获取了试用序列号,申请了正版证书。 intel 还是蛮良心的!
本来想直接用 icc/ifort 编译 octave 然后直接链接 Intel® mkl 库的,但是这篇文章[1]的发表日期是 2011 年,已经过时了诶!无奈自己 hack octave 的 configure 文件半天,但是发现只要编译过程中加了 -lsvml (由 intel-compiler-base 提供该动态库)选项,无论 icc/gcc,程序就会吐核,而且吐核的地方居然是在 main 前,猜测大概在 load 库的时候就挂了,所以编译 octave 的 ./configure 过程中各种测试没法通过,Intel 的一堆库都是私有软件,完全不知道问题出在哪里,最后不得不放弃 :'(
后来想直接找 Intel® mkl 中的 lib*{blas,lapack}.so 来替换掉 /usr/lib 中的 lib{blas,lapack}.so 以达到利用 mkl 的效果,无奈只找到 libmkl_blas95_{i,}lp64.a libmkl_lapack95_{i,}lp64.a libmkl_core.a 几个看起来还有点像的的文件,然而这全都是静态库啊!
再次准备放弃的时候,无意间看到了这篇文章[2]!这是什么?*How to create a standalone MKL version of BLAS and LAPACK shared libraries?* 啊哈哈!不正是我要找的东西吗!
大概的原理是,由于 Intel® mkl 的几个核心库是 PIC 的,所以可以从中构建自己需要的 .so 动态库[3]!
所以呢,照着这篇文章的内容,很容易就搞到了需要的几个库!完全没有难度嘛!首先用之前下好的 parallel_studio_xe_2016 和 AUR 的 intel-parallel-studio-xe 打好包,安装其中的 intel-mkl 和 intel-compiler-base 两个包,其实不装后者似乎也没关系,后者提供了几个软链,方便后面的 cp 操作,仅此而已……
建立一个工作目录,叫 intel-mkl 好了!
mkdir intel-mkl && cd intel-mkl
然后把需要的东西拷贝过来,intel 比较良心,已经把 blas、cblas 和 lapack 的符号表列表写好了,所以直接 cp 过来用就好了!
cp /opt/intel/mkl/tools/builder/{makefile,blas_example_list,cblas_example_list,dft_example_list,lapack_example_list} .
之后 make 三次就可以得到需要的 .so 了!
make libintel64 export=blas_example_list interface=lp64 threading=parallel name=libblas_mkl
make libintel64 export=lapack_example_list interface=lp64 threading=parallel name=liblapack_mkl
make libintel64 export=cblas_example_list interface=lp64 threading=parallel name=libcblas_mkl
其实 octave 不依赖 cblas,不过既然有,顺便提取一下好了!关于上面的参数,这里[5]和 make help 都有详细的解释。关于 lp64 和 ilp64 的区别,这里[6]有讲,大概就是 lp64 的 int 类型是 32 位的,ilp64 的 int 是 64 位的。
接下来就要提取 fftw3 了,以下提取方法实在很不正确,所以如果各位有什么更好的方法欢迎告诉我呀!fftw 一共提供了 9 个 .so 文件,如下:
$ ll /usr/lib/libfftw3{,l,f}{,_omp,_threads}.so |awk '{print $9$10$11}'
/usr/lib/libfftw3f_omp.so->libfftw3f_omp.so.3.4.4*
/usr/lib/libfftw3f.so->libfftw3f.so.3.4.4*
/usr/lib/libfftw3f_threads.so->libfftw3f_threads.so.3.4.4*
/usr/lib/libfftw3l_omp.so->libfftw3l_omp.so.3.4.4*
/usr/lib/libfftw3l.so->libfftw3l.so.3.4.4*
/usr/lib/libfftw3l_threads.so->libfftw3l_threads.so.3.4.4*
/usr/lib/libfftw3_omp.so->libfftw3_omp.so.3.4.4*
/usr/lib/libfftw3.so->libfftw3.so.3.4.4*
/usr/lib/libfftw3_threads.so->libfftw3_threads.so.3.4.4*
由于我比较懒,又分不清这些 l 啦 f 啦 threads 啦 omp 啦有什么区别,所以索性全部提取到一个 .so 里好了(雾),如果这个方法不对求打脸 QuQ。
首先从这堆 .so 里提取符号表,生成 fftw_list 文件:
nm -D /usr/lib/libfftw3{,l,f}{,_omp,_threads}.so |grep -v ' w ' | grep -v ' U '| sed -n 's#.* \([[:alnum:]_]\+$\)#\1#p' |sort |uniq > fftw_list
然后像之前一样 make 就好啦!其中两个 grep 分别是去除弱符号和未定义符号,否则会出现 undefined symbol: _Jv_RegisterClasses 之类的错误……
make libintel64 export=fftw_list interface=lp64 threading=parallel name=libfftw3_mkl
得到 libfftw3_mkl.so 之后用 nm -D 查看了一下符号表,发现似乎比之前生成的 fftw_list 里面少了一些东西,现在还不知道有没有影响……反正 octave 的 fft/fft2/ifft/ifft2 都是能工作的 ~\(≧▽≦)/~
此时我们就得到了 4 个需要的 .so,分别是 libblas_mkl.so libcblas_mkl.so libfftw3_mkl.so liblapack_mkl.so,然后呢? strip 这 4 个文件,扔到 /usr/lib 里,然后再做几个软链接就完成啦!
3. Tips & Tricks
3.1 OpenMP 依赖
其实生成的这几个文件,可能会依赖 Intel 的 OpenMP 库:
$ ldd libblas_mkl.so
...
libiomp5.so => /opt/intel/lib/libiomp5.so (0x00007f79b66d5000)
...
我可不想用这几个库的时候还要安装 intel-compiler-base 这一大坨依赖!这是由于在 make 的时候没有安装 OpenMP 造成的,所以只需要安装 openmp 就好啦~
$ sudo pacman -S openmp --asdeps
...
$ make ...
$ ldd libblas_mkl.so
...
libiomp5.so => /usr/lib/libiomp5.so (0x00007f560b86b000)
...
3.2 写一个 PKGBUILD
由于用 pacman 安装软件的时候会有依赖要求,所以写一个 PKGBUILD 来提供 lapack cblas blas 和 fftw 的依赖啦,还可以统一管理文件!
pkgname=libmkl
pkgver=11.3.3
pkgrel=1
pkgdesc="dynamic libraries extracted from intel mkl library, privides fftw, blas, cblas and lapack"
arch=('x86_64')
depends=('openmp')
url=""
license=('custom')
provides=('lapack' 'cblas' 'blas' 'fftw')
conflicts=('lapack' 'cblas' 'blas' 'fftw')
source=("libmkl.tar")
md5sums=('460f3cfbf0af09628a17e1ddde78631f')
PKGEXT=.pkg.tar
package() {
cd "$pkgname"
mkdir -p "$pkgdir"/usr/lib
cp -P * "$pkgdir"/usr/lib
}
其中 source 里的 libmkl.tar 当然就是包含四个 .so 和一堆符号链接的 tarball 啦。大概长这个样子,里面除了 *_mkl.so 外都是符号链接。
$ tar -tf libmkl.tar
libmkl/
libmkl/liblapack_mkl.so
libmkl/libfftw3_mkl.so
libmkl/libcblas_mkl.so
libmkl/libblas_mkl.so
libmkl/libfftw3.so.3
libmkl/libfftw3_omp.so.3
libmkl/libfftw3_threads.so.3
libmkl/libfftw3f.so.3
libmkl/libfftw3f_omp.so.3
libmkl/libfftw3f_threads.so.3
libmkl/libfftw3l.so.3
libmkl/libfftw3l_omp.so.3
libmkl/libfftw3l_threads.so.3
libmkl/libfftw3f_omp.so
libmkl/libfftw3f.so
libmkl/libfftw3f_threads.so
libmkl/libfftw3l_omp.so
libmkl/libfftw3l.so
libmkl/libfftw3l_threads.so
libmkl/libfftw3_omp.so
libmkl/libfftw3.so
libmkl/libfftw3_threads.so
libmkl/libblas.so
libmkl/libcblas.so
libmkl/liblapack.so
libmkl/libf77blas.so
libmkl/liblapack.so.3
libmkl/libblas.so.3
3.3 使用 OpenMP 的多线程
参考这里[7]啦。其中比较主要的几个环境变量是:
OMP_NUM_THREADS: 设置 OMP 的线程数量
MKL_NUM_THREADS: 设置 MKL 的线程数量
MKL_DYNAMIC: 设置为 true 会 reduce possible oversubscription from MKL threading[7]。
注意缺省状态或设置 MKL_DYNAMIC 为 true 会导致线程数量不是最大。之前我设置为 true 之后 octave 进行远算时始终只有两个线程,设置为 false 之后跑满了 4 个线程。然而!!!我在跑满 4 个线程和跑 2 个线程运行矩阵乘法远算,速度居然是一样的!所以感觉设置为 true 来 reduce oversubscription 应该还是有用的。
关于 OMP_NUM_THREADS 和 MKL_NUM_THREADS 两者效果上的区别我也不是太懂,有人说设置后者会屏蔽前者。但是[7]包含如下一段话:
Example:
Calls MKL routines dsyev from one own OpenMP* parallel region with 2 OpenMP threads and 8 MKL threads.
MKL_DYNAMIC=false
OMP_NESTED=true
OMP_NUM_THREADS=2 MKL_NUM_THREADS=8
所以我也不太清楚啦~反正我二者设置的都是 4。
4. 性能对比
有空再补上吧,写不动了……
5. 分享制作好的 .so
等我先看看 EULA 看能不能分发再说……
6. 可能的 bug
6.1 当然是打好的 PKGBUILD 没有各种 .h 啦!所以虽然写了 provides,但是如果本地要编译某些依赖 blas cblas lapack fftw 的程序的时候肯定是找不到头文件的啦!
6.2 就是制作 libfftw3*.so 的手法太脏了……求修改意见……
好了就这样吧,先写到这里吧。
References:
[1] https://software.intel.com/en-us/articl … gnu-octave
[2] http://wiki.science.ru.nl/cncz/Intel_co … raries_.3F
[3] http://www.theochem.ru.nl/composerxe/Do … /index.htm
[4] https://aur.archlinux.org/pkgbase/intel … studio-xe/
[5] http://www.theochem.ru.nl/composerxe/Do … meters.htm
[6] https://software.intel.com/en-us/node/528682
[7] https://software.intel.com/en-us/articl … plications
2016-11-02 Update:
今天在准备打算学习 numpy 的时候出现了问题!Intel 给的几个 *_example_list 中定义的符号表不全,导致在 import numpy 的时候会提示 undefined symbol,所以又稍微折腾了一下。缺少的几个 symbol 如下:
cblas_zdotc_sub
cblas_zdotu_sub
cblas_cdotc_sub
cblas_cdotu_sub
dcopy_
前四个在 cblas 库中,最后一个在 blas 库中。所以只需要在 make 之前,分别在 cblas_example_list 和 blas_example_list 文件中添加前四个和最后一个符号,再 make 就好了。
另外,numpy 只会加载 liblapack.so 库,而我们生成的 liblapack.so 并没有动态链接到 libblas.so, 所以完成上述步骤后 numpy 依然不能找到 dcopy_ 符号,因此还需要重新提取 liblpack.so 使其动态链接到 libblas.so。所以在生成 liblpack.so 这步需要修改 makefile,在 makefile 中的 gcc 编译选项的 -L ... $(LIBM) 选项后添加 -lblas。
2016-11-03 Update:
今天发现 intel 给的 example_list 少了好多好多符号,建议还是按照生成 fftw_list 的方法重新生成 list。
最近编辑记录 zsrkmyn (2018-05-02 12:50:24)
离线
话说 BBCode 的 code 标签代码高亮可以调么 =.=
离线
离线
话说 BBCode 的 code 标签代码高亮可以调么 =.=
不能……欢迎 pr~
离线
2025 年了,无意间翻到 9 年前写的东西,死去的回忆突然攻击我。巧合的是,毕业之后还去 intel compiler 团队工作了一段时间,碰巧有机会和 mkl 团队有少部分的合作。当时写文章时好多不懂的东西也有了更多的理解。
但是发现只要编译过程中加了 -lsvml (由 intel-compiler-base 提供该动态库)选项,无论 icc/gcc,程序就会吐核,...
svml 实际上是 intel 提供的 Short Vector Math Library,用 sse*/avx* 指令集提供了向量化版本的数学库,比如向量化版本的 sin、cos、sqrt 等。如果用 intel compiler (icc/icx/ifort/ifx) 编译程序,如果启用了自动向量化,调用 libm 中的数学函数有机会被替换为向量化 svml 中的函数。svml 实际提供了静态库和动态库两个版本,如果当时把 svml 改为静态链接,也许能解决这个问题。如果用 gcc,svml 库中的东西应该压根用不到……
关于 lp64 和 ilp64 的区别,这里[6]有讲,大概就是 lp64 的 int 类型是 32 位的,ilp64 的 int 是 64 位的。
很遗憾,[6] 的链接失效了。不过 webarchive 上有存档 。不过直到工作之后我才知道,ilp64 中的 ilp 表示 int long pointer,所以 ilp64 是指 int long pointer 都是 64-bit。类似地,ilp32 是指 int long pointer 是 32-bit,lp64 是指 long pointer 是 64-bit。
离线
页次: 1