使用MPI编译Linux平台下使用的并行SuperLU静态链接库
Author: Zengqiang Date:2013-11-11
为了能在32位和64位Linux平台下能够调用并行的SuperLU库函数,求解Ax=b这种线性方程系统,这里采用MPI自带的mpicc和mpif77编译器编译在Linux平台下用静态链接库。
Step1下载安装MPI
MPI(Message Passing Interface)是一个标准并行通信接口,常用的MPI工具包有MPICH2/IntelMPI,包含C/C++与Fortran语言的并行调用接口库函数、头文件以及编译工具集mpicc、mpicxx、mpif77、mpif90等等。
这里我们采用MPICH2,下载MPICH2工具包:
http://www.mpich.org/static/downloads/1.3.2p1/
or http://www.mpich.org/static/downloads/1.3.2p1/mpich2-1.3.2p1.tar.gz
当然也可以选择其他版本,这里采用mpich2-1.3.2p1作为演示.
这里使用的是Ubuntu-9.10的Linux发行版,内核版本为2.6.34.13,GNU-GCC采用的版本为4.4。
首先进入$HOME文件夹下,建立一个名为MPI的文件夹并进入:
cd $HOME
mkdir MPI
cd MPI
将mpich2-1.3.2p1.tar.gz拷贝到MPI文件夹下。
将其解压:
tar –zxvf mpich2-1.3.2p1.tar.gz
产生一个文件夹mpich2-1.3.2p1,再另外建立两个文件夹:
mkdir mpich2-1.3.2 mpich2-install
此时MPI文件夹下如下图所示:
这三个文件夹中,mpich2-1.3.2p1包含tar解压出来的源文件,而mpich2-1.3.2准备用作build使用,而mpich2-install则准备用作mpi的安装目录。
进入mpich2-1.3.2p1,配置编译安装如下:
cd mpich2-1.3.2p1
./configure --prefix=$HOME/MPI/mpich2-install |& tee c.txt
make
make install
PATH=$HOME/MPI/mpich2-install/bin:$PATH ; export PATH
最后一句命令是设置环境变量,上述安装需要你的Linux中安装有f77/g77/gfortran等Fortran编译器,建议在安装前检查是否装有gfortran:
上图说明gfortran已经安装,如果没有安装,会出现相应的提示。
如果未安装,建议先下载安装GNU-GCC的完整编译器工具集:
http://ftp.gnu.org/gnu/gcc/gcc-4.4.1/
or http://ftp.gnu.org/gnu/gcc/gcc-4.4.1/gcc-4.4.1.tar.gz
完成make install之后,libmpich.a等lib库文件被安装在mpich2-install/lib文件夹下,mpi.h等头文件在mpich2-install/include文件夹下,mpicc等编译器被安装在mpich2-install/bin文件夹下。
为了方便调用mpi的编译器,在/usr/local/bin中建立几个mpi常用编译器的硬链接(非Ubuntu系统不用加sudo哦~):
cd /usr/local/bin
sudo ln $HOME/MPI/mpich2-install/bin/mpicc $HOME/MPI/mpich2-install
/bin/mpicxx $HOME/MPI/mpich2-install/bin/mpif77 ./
至此,mpi就已经安装完毕,用which命令测试编译器:
which命令可以返回可执行文件的路径,如上图所示,mpi已经安装完毕。
Step2安装CMake2.8
由于并行SuperLU依赖的METIS/ParMETIS库的安装需要CMake2.8,所以这里安装一下CMake2.8。
CMake是一款交叉编译工具,在管理大型源代码工程、灵活地产生Makefile和跨平台编译连接方面有明显优势。
首先,下载官方提供的CMake2.8:
http://www.cmake.org/cmake/resources/software.html
or http://www.cmake.org/files/v2.8/cmake-2.8.12.1.tar.gz
这里下载的是目前最新的版本cmake-2.8.12.1.tar.gz。
将其拷贝到$HOME目录下,解压并进入文件夹:
tar –zxvf cmake-2.8.12.1.tar.gz
cd cmake-2.8.12.1
如图所示:
配置编译安装:
./bootstrap
make
make install
采用默认安装即可,非常方便,也无需手动添加环境变量。
测试cmake:
OK,Go to the next !
Step3安装METIS& ParMETIS库
METIS是一款非结构化图形划分、有限元网格划分、稀疏矩阵降秩填充的串行版程序,而ParMETIS是其并行版本,主要基于多级递归二分法、多级k路图、多约束划分等算法,适用于大规模数值计算,详情请见官网:
http://glaros.dtc.umn.edu/gkhome/views/metis
METIS和ParMETIS库会被并行的SuperLU调用,因此我们需要安装这两个包,新版的ParMETIS中都包含有METIS,因此我们只需要下载ParMETIS包即可:
http://glaros.dtc.umn.edu/gkhome/fetch/sw/parmetis/parmetis-4.0.3.tar.gz
or http://glaros.dtc.umn.edu/gkhome/metis/parmetis/download
这里我们下载了parmetis-4.0.3.tar.gz,将其拷贝到$HOME目录下,解压并进入文件夹:
tar –zxvf parmetis-4.0.3.tar.gz
cd parmetis-4.0.3
ls
通过查看BUILD.txt文件,找到默认配置和方法,在该目录下:
make config
make
make install
我依次敲入上述三条命令后,没有任何报错,正常安装完毕,如果你也正常安装完毕,这里通常情况下无论是32位还是64位机器都不需要修改配置。如果你使用的是64位机器,那么这里如果你的整型想使用64位整型(_int64),而浮点型想使用64位双精度型(double),那么请打开./metis/include/metis.h文件修改宏定义:
vim ./metis/include/metis.h
除非是用到超超级大规模计算问题,矩阵规模行列大于2^31-1=21,4748,3647,才有必要将IDXTYPEWIDTH设为64,这可是有21亿啊~~!!!当然,双精度数据倒是经常会用到,想要使用double型64位浮点数精度,那就果断把REALTYPEWIDTH改为64吧!!!修改后重新make config;make;makeinstall即可。
但是我在另一台计算机上安装时,却在make这一步出了问题,终端上输出了一个错误告警,并且中止了make,该错误描述如下:
cc: error: unrecognized command line option "-Wno-unused-but-set-variable"
通过各大论坛,了解到该选项-Wno-unused-but-set-variable是编译器的警告选项,用于对源程序中定义但未使用的变量告警,并中断make编译过程,该选项只被GNU-GCC4.4及以上的版本支持,而这台安装出错的计算机上的GCC版本比较老了,所以不支持该选项,我的解决方案是找到设置该选项的文件,把该选项去掉。(当然你也可以选择更新你的GNU-GCC版本,只是这个安装要耗费更多时间)
那么,先要找到设置该选项的文件:
grep –ri wno-unused-but-set-variable ./
该命令启用正则表达式匹配器,查找当前目录及其子目录(-r选项)下包含字符串’wno-unused-but-set-variable’的文件,忽略大小写(-i选项),并且打印所在文件的相对路径以及字符串所在行到屏幕上。
从上图可以看到,在./metis/GKlib/GKlibSystem.cmake文件中包含有一句set()语句,语句中标红的正是我们要找的选项,后缀.cmake的文件是CMake编译时的一种输入配置文件,那么我们基本上可以确定在该文件中去掉该选项的设置即可达到目的。
打开GKlibSystem.cmake文件内容,找到该选项:
vim ./metis/GKlib/GKlibSystem.cmake
这里我使用了vim编辑器打开该文件,其他编辑器亦可。
找到第36行:
发现了我们要找的选项了,把-Wno-unused-but-set-variable选项删除并保存,退出文件。
此时在parmetis顶层目录下,让我们再来执行:
make config
make
make install
OK,no problem!!!parmetis安装完成。
当然,装好parmetis后,可别把metis忘了。
在parmetis-4.0.3顶层目录下,进入metis文件夹:
cd metis
make config
make
make install
重复之前的配置和安装命令,安装完毕。
Metis和parMetis都安装好后,让我们来查看一下这两个库:
ls /usr/local/lib
找到libmetis.a和libparmetis.a了吧~_~
当然,如果有需要头文件,可以去/usr/local/include文件夹中查看:
ls /usr/local/include
Now, theStep 4~
Step4编译安装并行SuperLU库
SuperLU是一款用于高端计算机的大型稀疏非对称线性方程系统的直接求解器,源码由C实现,接口可以被C/Fortran调用。
SuperLU的官方资源:
http://crd-legacy.lbl.gov/~xiaoye/SuperLU/
该官方资源提供了三种SuperLU版本:
SuperLU为串行版本、SuperLU_MT为内存共享式的并行版本、SuperLU_DIST为分布式内存的并行版本。
其中SuperLU串行版本不适合用于大型数值计算,而SuperLU_MT受到共享内存资源的大小限制,扩展性不太好,而SuperLU_DIST由于采用了分布式内存的方式,扩展性很好,内存不够了,就多装几个胖节点或刀片机服务器,经过简单的网络设置,即可增加集群服务器的内存容量,是大规模数值计算的最优选择!
OK,no more wastes,let’s begin!
首先,下载SuperLU_DIST版本:
http://crd-legacy.lbl.gov/~xiaoye/SuperLU/superlu_dist_3.3
or http://crd-legacy.lbl.gov/~xiaoye/SuperLU/#superlu_dist
这里选择安装下载superlu_dist_3.3版本。
将其拷贝到$HOME文件夹下,解压并进入该文件夹:
tar –zxvf superlu_dist_3.3.tar.gz
cd SuperLU_DIST_3.3
ls
通过查看README文件可以找到配置和安装的方法。
首先,从MAKE_INC文件夹下找到适合自己机器的make.inc配置文件,并将其拷贝出来覆盖顶层目录的make.inc文件,查看一下MAKE_INC文件夹下的内容:
ls MAKE_INC
该文件夹下有几种常用的系统的make.inc配置,我们常用的基本上是i386/i686/X86_64的intel架构处理器,而其他架构下的处理器即便该文件夹下没有,也可以拷贝一个最相似的文件出来做一些相应的修改即可。
查看自己的处理器架构类型:
uname –m
上图所示,本人的cpu类型为i686,说明是32位处理器系统,而64位处理器,则会显示X86_64,但MAKE_INC文件夹下既没有i686也没有X86_64,其实如果你看了make.inc文件,你就知道没有很大关系,里面都是一些编译器和路径的设置,做出相应的修改即可完成make.inc的配置。
在这里,由于i386是最相似的类型,那么将i386的配置文件拷贝到顶层目录覆盖make.inc文件:
cp ./MAKE_INC/make.i386_linux make.inc
此时原来顶层目录的make.inc已被替换。
打开make.inc文件,让我们来修改它!
vim make.inc
第一步,修改
PLAT = _i686 (根据自己的系统设置哦~)
第二步,修改
DSuperLUroot = ${HOME}/SuperLU_DIST_3.3
第三步,注释掉BLASDEF这一行
#BLASDEF = -DUSE_VENDOR_BLAS
第四步,修改
BLASLIB = ../lib/libblas$(PLAT).a
第五步,修改
METISLIB = -L/usr/local/lib -lmetis
PARMETISLIB = -L/usr/local/lib -lparmetis
OK,修改完毕!看看此时的make.inc文件:
保存并退出该文件,在顶层目录执行:
make blaslib
执行完该命令会在./lib文件夹下编译链接生成了BLAS的lib:
ls lib/
上图中,libblas_i686.a则是BLAS的静态链接库了!
OK,在顶层目录继续执行:
make
编译链接成功后,在lib文件夹下会生成SuperLU的并行库了!
ls lib/
至此,当然已经可以使用分布式并行的SuperLU库求解了!
先进入EXAMPLE文件夹来做一个测试吧!该文件夹的测试用例也随着make一并被编译链接成了可执行文件:
cd EXAMPLE/
ls
上图中有很多测试用例,可以通过查看该文件夹下的README文件了解详细信息,我们这里采用pddrive这个应用程序来测试并行的SuperLU求解器,该应用程序主要使用了PDGSSVX函数来求解线性系统方程Ax=b,A这里为实数型矩阵,该文件夹下提供了3中输入文件,我们使用其中的big.rua实数矩阵数据文件作为输入文件,该矩阵的规模为4960×4960.
在EXAMPLE目录下,执行:
mpiexec –n 4 ./pddrive –r 2 –c 2 ./big.rua
终端输出了一些计算结果的统计信息,其中Solve time显示为0.68秒,这里调用了4个进程,行处理和列处理都安排了2个进程。
在这里不讨论并行superlu的求解和调用接口,函数接口的详细使用方法请参考pddrive.c文件源码与用户手册。
除了实数型的测试用例,还有复数系统矩阵的测试用例pzdrive!!!
在EXAMPLE目录下,执行:
mpiexec –n 4 ./pzdrive –r 2 –c 2 ./cg20.cua
cg20.cua是提供的复数矩阵数据文件,矩阵大小为400×400!
All right~到这里已经测试完毕,说明库的调用完全没有问题!!!
但是为了方便以后使用默认路径调用库,我们将lib文件下的两个静态链接库拷贝到/usr/local/lib文件夹下吧!
从EXAMPLE文件夹回到顶层目录:
cd ..
sudo cp ./lib/*.a /usr/local/lib
ls /usr/local/lib
找到libblas_i686.a和libsuperlu_dist_3.3.a了吧~
OK,the last time!
Step5建立自己的工程调用并行SuperLU库
虽然已经可以调用SuperLU的并行库来求解了,但每次都在EXAMPLE文件夹下操作就太别扭了吧,而且对于那些不熟悉Makefile编译管理的孩纸们来说,这一节无论如何都是有必要的~
我们这一节的目标是在linux系统的任意位置建立一个自己的文件夹,然后呢,写好一个属于自己的makefile,来编译你的程序,并成功地调用起并行SuperLU库来求解!
为了方便各位重复该节工作,我们仍采用EXAMPLE中的pddrive.c程序来做演示。
通过查看pddrive的makefile发现,该程序的实现还需要同一目录下的dcreate_matrix.c和sp_ienv.c文件,将这三个c文件拷贝到你自己建立的一个文件夹,我在$HOME/Desktop/mpi_home/下建立了一个文件夹,名为myParSuperLUproj,用来存放我的整个工程文件。
打个比方,这3个文件就是你自己编写的用于求解Ax=b问题的源程序,那么还需要输入数据文件,我们将刚才使用的big.rua也拷贝过来,同时建立一个Headers文件夹用来存放所有需要的头文件,再建立一个名为Makefile的空文件,后面用于make编译链接。
cd $HOME/Desktop/mpi_home/myParSuperLUproj
mkdir Headers
touch Makefile
ls
架子搭好了,然后添砖加瓦!
由于要使用并行SuperLU的库文件,免不了要调用SuperLU的头文件,将$HOME/SuperLU_DIST_3.3/SRC下的.h文件统统拷贝到Headers文件夹中:
cp $HOME/SuperLU_DIST_3.3/SRC/*.h ./Headers
ls ./Headers
如上图所示,总共11个文件(本来是12个,其中有一个名称为html_mainpage.h的头文件根本没被使用,所以删掉了~)~
如果你还记得step3的最后,我们查看了一下metis和parmetis的头文件,就会发现我们已经将其安装在了/usr/local/include文件夹下,该路径是可以被编译器自动搜索到的,所以这里不再将metis.h和parmeist.h拷贝到Headers文件夹下了!当然,实际上,我们也可以把superlu的所有头文件放在/usr/local/include下,这样可以使得工程更简洁,不过这里为了方便查看头文件的内容与结构,还是将superlu的头文件都放在了Headers中咯~
至此,所有的文件都已经准备好了,让我们来编写Makefile吧!
打开一开始建立的Makefile空文档:
vim Makefile
在这里直接给出写好的Makefile文件内容,请各位参考:
代码很简短,只有33行,结构也比较清晰,容易理解,下面简单介绍一下该Makefile各条语句的作用,方便大家在做自己的工程文件时有所参照。
----------------------------------------------------------------------------------------------
INCLUDEDIR = -I./Headers
(这里定义了一个INCLUDEDIR变量用来存储头文件路径,-I是编译选项,用来包含头文件路径)
----------------------------------------------------------------------------------------------
DSUPERLULIB = /usr/local/lib/libsuperlu_dist_3.3.a
BLASLIB = /usr/local/lib/libblas_i686.a
METISLIB = /usr/local/lib/libmetis.a
PARMETISLIB = /usr/local/lib/libparmetis.a
(这里定义了4个变量用来存储库文件的绝对路径及名称)
LIBS = $(DSUPERLULIB) $(BLASLIB) $(PARMETISLIB) $(METISLIB)
(这里又定义了1个变量用来包含上述4个库文件)
(注意:这里的顺序尽量按照上述顺序写,静态库的链接顺序是有要求的,越底层调用的库越要包含在靠后的位置,这里superlu调用了blaslib和parmetislib,而parmetislib又调用了metislib)
----------------------------------------------------------------------------------------------
CC = mpicc
CFLAGS = -pipe –O2
CDEFS = -DAdd_
FORTRAN = mpif77
F90FLAGS =
LOADER = mpif77
LOADOPTS =
(这里又定义了7个变量用来设置编译器,CC为c的编译器,选择了mpicc;CFLAGS为mpicc编译器的选项,-pipe选项表示使用管道加速编译过程,-O2表示编译过程对源代码会进行2级优化;CDEFS设置了一些预编译定义选项,-DAdd表示Fortran希望c程序的名字后面带一个下划线;
Superlu的手册中有提到该选项:
FORTRAN编译器采用了mpif77编译器,其他选项则默认为空,方便提供给大家扩展使用,事实上,没有特别要求,这一部分的内容都不需要修改哦~)
----------------------------------------------------------------------------------------------
OBJS = pddrive.o dcreate_matrix.o sp_ienv.o
all : pddrive
pddrive : $(OBJS) $(DSUPERLULIB)
$(LOADER) $(LOADOPTS) $(OBJS) $(LIBS) –lm –o $@
(这里又定义了1个OBJS变量用来包含所有的object文件,一个c或fortran文件就会编译出一个.o文件,all以及pddrive则代表make程序可用的输入参数,例如:你可以使用 make all 编译所有目标文件,也可以使用make pddrive只编译pddrive目标文件,该名称可以自行设定。
在makefile编程中,一个冒号语句代表一个规则,冒号“:”前面代表目标,冒号后面代表依赖,all:pddrive的意思就是,想要编译all目标,需要先有pddrive,而pddrive : $(OBJS) $(DSUPERLULIB)的意思是,想要编译pddrive目标,那么冒号后面的$(OBJS)和$(DSUPERLULIB)就必须存在,冒号语句的下一行语句则代表该冒号语句一旦符合条件就执行的命令,
$(LOADER) $(LOADOPTS) $(OBJS) $(LIBS)–lm –o $@语句中的-lm的意思是要链接libm.a这个数学库,该库被存放在/usr/lib文件夹下,编译器可以自动搜索到该路径,因此不需要包含其所在路径,而$@则是make中的自动化变量,表示规则的目标,例如pddrive : $(OBJS) $(DSUPERLULIB)冒号规则语句,规则的目标文件名就是pddrive。
事实上,虽然这句话的LOADER使用了mpif77,其实你使用mpicc也可以,这条命令在这里只是执行了一个链接操作,并没有编译的过程,因为$(OBJS)在这里是必须要存在的,也就是说在此之前要编译好所有的.o文件,当然这个编译任务由下面的语句来执行了)
----------------------------------------------------------------------------------------------
.c.o:
$(CC) $(CFLAGS) $(INCLUDEDIR) –c $< $(VERBOSE)
.f.o:
$(FORTRAN) $(FFLAGS) $(INCLUDEDIR) –c $< $(VERBOSE)
(这里使用的冒号规则与前面不一样,这是一个双后缀规则,.o和.c都是make可以识别的文件类型,双后缀规则会认为所有的.o文件的依赖文件是.c文件,后面的.f.o也同样如此,这种规则的好处是make可以自动识别出c文件把它编译成相应的.o文件,将.f文件也编译成.o文件,依赖关系自动寻找,非常方便。
冒号规则下的命令,就是在编译c文件和f文件为.o文件,$<和$@类似,也是自动化变量,只不过$<代表的是规则的依赖文件名,意思就是一个叫abc.c的c文件是abc.o目标文件的依赖。
$(VERBOSE)代表make执行时会输出“basic”级别之上的调试信息,包括解析的makefile文件名,不需要重建文件等。)
----------------------------------------------------------------------------------------------
clean:
rm –f *.o
(这句话够简单了吧,意思就是执行make clean命令时,要执行rm –f *.o这条命令,删除当前文件夹下的所有.o文件)
----------------------------------------------------------------------------------------------
到这里,这个简单的Makefile就分析完了,你可以发挥自己的DIY天赋去建立一个自己的工程了,简单来说,如果你有几个c文件和.h文件,把.h文件放在Headers文件夹下,修改一下Makefile中第19行到23行的部分名字,就可以编译你自己的工程了。
如上图所示,修改一下OBJS后的.o文件名称为你自己的.c文件对应的名称,all后面的pddrive和第23行的pddrive可以自己设定名称啦,最后编译出来的可执行文件就是这个名称。
好,来看看效果吧,进入工程的顶层目录,执行:
make
ls
如上图所示,每个.c文件有个对应的.o文件,最后的可执行文件名称为pddrive,通常来说不需要这些.o文件了,那么执行:
make clean
ls
Now,干净了些吧,接下来运行下可执行文件吧!
mpiexec –n 4 ./pddrive –r 2 –c 2 ./big.rua
如下图所示,程序正常运行,调用成功!Congratulations!!!
Well,希望能帮助到各位,文中有不对之处还请指正哦~
电子邮箱:zengqiang007@126.com
欢迎讨论~
Zeng qiang
2013-11-12