在CentOS上安装MPICH以及MPI入门编程笔记
一、下载并安装mpich
1、下载安装包
(1)准备工作
// 更新yum库 yum update // 下载相关依赖包 yum install wget -y yum install gcc gcc-c++ gcc-fortran kernel-devel -y
(2)官网下载MPI安装包(http://www.mpich.org/downloads/)
2、解压编译安装
(1)一般的编译安装
# 解压 tar -zxvf mpich-4.0.2.tar.gz # 进入目录并编译安装 cd mpich-4.0.2/ ./configure --prefix=/usr/local/mpich-4.0.2 --with-device=ch4:ofi 2>&1 | tee 1.log # ------------ps:下边两步要运行很久,可以泡杯咖啡休息等待哦----------- make && make install
(2)使用脚本后台编译(vim cm.sh)
脚本文件./cm.sh > 1.log 2>&1 &(后台运行)
#!/bin/sh set -ex ./configure CC=/usr/bin/gcc \ CXX=/usr/bin/g++ \ FC=/usr/bin/gfortran \ F77=/usr/bin/gfortran \ --prefix=/usr/local/mpich-4.0.2 # --build=sw_64sw6a-sunway-linux-gnu \ # --host=sw_64sw6a-sunway-linux-gnu \ # --target=sw_64sw6a-sunway-linux-gnu #make -j 16 #make install
3、配置环境变量
(1)编辑环境变量(vim ~/.bashrc)
# 在最底层添加: MPICH=/usr/local/mpich-4.0.2 export PATH=$MPICH/bin:$PATH export LD_LIBRARY_PATH=$MPICH/lib:$LD_LIBRARY_PATH
令环境变量生效
source ~/.bashrc
mpirun -versions
mpiexec --version mpichversion
4、软件包的卸载
很多source的makefile都有写uninstall规则,直接在source里make uninstall就行。安装错误解决办法:
#make uninstall&&make clean
之后重新configure、make、make install操作
5、编译程序出错
i、Fatal Error: File 'mpi.mod' opened at (1) is not a GNU Fortran module fileii、/usr//bin/ld: warning: libgfortran.so.5, needed by /usr/local/lib/libmpifort.so, may conflict with libgfortran.so.3
【解决】:这样情况说明当前mpi的版本不是由当前gcc版本编译安装的。
例如我这里gcc当前版本是4.8.5,而我当前使用的mpi是由gcc-8.5.0编译的。
二、MPI入门编程笔记
1、第一个MPI小程序
vim hw.c
#include <stdio.h> #include "mpi.h" int main(int argc, char **argv) { int myrank,nprocs,len; MPI_Status status; //状态 char Processorname[20]; MPI_Init(&argc,&argv); //初始化MPI并行环境 MPI_Comm_size(MPI_COMM_WORLD, &nprocs); //nprocs返回进程个数 MPI_Comm_rank(MPI_COMM_WORLD,&myrank); //myrank返回进程号(从0开始) MPI_Get_processor_name(Processorname, &len); //返回机器名字和名字长度 printf("Hello world! Process %d of %d on %s.\n",myrank,nprocs,Processorname);
MPI_Finalize(); //终止MPI处理 }
2、在同一台机器编译运行
方法一:
mpicc hw.c // 编译(会生成一个a.out文件) ./a.out // 运行
方法二:
mpicc -o hw hw.c // 编译(会生成一个hw可运行文件) mpirun ./hw // 运行
mpirun -np 4 ./hw
3、在同一台机器编译运行
首先要保证hw.c是在共享目录下("相同目录"不会的翻看我之前的NFS文件挂载)正常编译即可
mpirun -np 8 -hosts david,client1,client2 ./hw
三、解决统信UOS上安装的错误
1、./configure出错
这个问题是:不识别系统架构
(1)解决方法1:
添加--build编译选项
./configure --build=sw_64-unknown-linux-gnu //sw_64sw6a-unknown-linux-gnu
将mpich源码包里所有的config.guess、config.sub替换掉
find /usr -name "config.guess" /usr/bin/cp /usr/share/misc/config.* ./
i、config.guess添加:
#xujb add SW sw_64:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in SW2) UNAME_MACHINE=sw_64sw2 ;; SW2f) UNAME_MACHINE=sw_64sw2f ;; SW2f*) UNAME_MACHINE=sw_64sw2f ;; SW*) UNAME_MACHINE=sw_64sw6a ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC="gnulibc1" ; fi echo "${UNAME_MACHINE}"-sunway-linux-"${LIBC}" exit ;;
ii、config.sub 中对应位置添加:
| sw_64-* | sw_64sw2-* | sw_64sw6a-* \
2、make和使用时排错
(1)make时出错【注意】:UCX可能存在不匹配,报错如下:
此时,需要关闭UCX(见https://github.com/open-mpi/ompi/issues/6640):
./configure --build=sw_64-unknown-linux-gnu --enable-mca-no-build=btl-uct
然后openmpi配置时,需加入--with-ucx=<UCX_INSTALL_PATH>
(2)检查或使用时出错(找不到libslurm.so.38或libmpi.so.12)
mpichversion: error while loading shared libraries: libmpi.so.12: cannot open shared object file: No such file or directorympiexec: error while loading shared libraries: libslurm.so.38: cannot open shared object file: No such file or directory
方法一:修改~/.bashrc或~/.bash_profile(~/.profile)或系统级别的/etc/profile
前两个只在root下生效,在普通用户下面依旧会提示错误,而添加在/etc/profile里是可以在普通用户下使用的,用source使其生效。
(source命令也称为“点命令”,也就是一个点符号(.)。source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录)
方法二:这个没有修改LD_LIBRARY_PATH但是效果是一样的实现动态库的查找,
i、/etc/ld.so.conf下面加一行/usr/local/lib
ii、保存后执行ldconfig生效
(ldconfig命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.)
方法二设置稍微麻烦,好处是比较不受用户的限制。
四、国产众核mpich绑核
每个CPU上有6个主核,每个主核带有64个从核,国产众核服务器的CPU编号为0-5;对于带有从核DMA的程序,必须绑定CPU。
1、修改mpich源码进行cpu绑核
自己写代码,要把进程绑定到CPU,要用sched_setaffinity函数。
(1)修改launch.c文件vim src/pm/hydra/lib/utils/launch.c // xujb #include <sched.h> #include <ctype.h> #include <string.h> #include <pthread.h> // xujb tpid = fork(); cpu_set_t cpumap; CPU_ZERO(&cpumap); CPU_SET(idx, &cpumap); sched_setaffinity(tpid, sizeof(cpumap), &cpumap);
(2)验证
mpirun -n 3 /mnt/sdb/source/system/yyj/bind/setcpu_nohy
2、使用taskset命令进行绑定
(1)下载taskset工具
yum install util-linux -y apt-get install schedutils
(2)带有众核的单进程作业需要用swrun-107来提交。
mpiexec -n 1 taskset -c 0 /bin/sw3run -b 1 -K 2048 -s 7000 -i ./setcpu_nohy
其中taskset -c可以指定CPU编号。国产众核服务器的CPU编号为0-5。
(3)带有众核或众核DMA的多进程作业需要绑定CPU。
由于mpiexec可以同时提交多个进程,所以无法直接使用taskset命令进行作业绑定,需要使用taskset.sh进行绑定。
srun -n 6 /bin/sh /root/taskset.sh /bin/swrun-107 -b 1 -K 2048 -s 7000 -E 64 -i ./setcpu_nohy
在X86宿主机向小型机提交mpi作业时,用到的环境变量是X86宿主机上的,即是X86宿主机的环境变量传到小型机上,小型机按照此环境变量执行命令,而X86宿主机和小型机的默认环境变量会有所不同(比如X86宿主机PATH环境变量中可能没有“/bin”,小型机上很多命令在“/bin”目录,默认使用X86宿主机的环境变量就会使得在小型机上很多命令找不到)
所以,在X86宿主机向小型机提交mpi作业时,除了mpiexec命令本身不需要带绝对路径,其他命令需要带绝对路径,包括“sh”,“swrun-107”,“taskset.sh”,“可执行程序”等。
五、国产众核在x86上的交叉编译环境
1、配置swgcc、mpicc环境
(1)配置环境变量/usr/sw下共有lib、mpi、penv、swgcc四个文件夹
SWGCC1=/usr/sw/swgcc/swgcc710-tools-SEA-1307/usr SWGCC2=/usr/sw/swgcc/swgcc710-tools-SEA-1307/usr_sw SWMPI=/usr/sw/mpi/mpi_20220608_SEA export PATH=${SWMPI}/bin:${SWGCC1}/bin:${SWGCC2}/bin:$PATH export LD_LIBRARY_PATH=${SWMPI}/lib/single_dynamic:${SWGCC1}/lib:${SWGCC1}/lib_for_gcc/lib:$LD_LIBRARY_PATH
scp /usr/sw/swgcc/swgcc710-tools-SEA-1307/swrun-107 mcn01:/usr/bin/
mpirun -n 1 /usr/bin/swrun-107 -b 1 -c 0 -E 64 -s 12000 -K 1024 -i ./main_run
2、自己利用swgcc编译mpich交叉编译环境
mpich-4.0.2安装包里面,若无法配置fortran(--disable-fortran或--enable-fortran=none)
--build在x86平台编译;--host=sw_64在众核平台上运行。
./configure CC=swgcc CXX=swg++ FC=swgfortran \ LDFLAGS="-mhybrid -L/usr/sw/lib -lswverbs_ft_perf -lswutils -L/usr/sw/lib -lswtm -lm -Wl,-zmuldefs -ldl -lrt -lpthread" \ --prefix=/usr/sw/mpi/mpi_20220301_SEA \ --host=sw_64-unknown-linux-gnu \ --target=sw_64-unknown-linux-gnu \ --with-cross=/home/xujb/cross_file \ --enable-shared=no --enable-static=yes \ --with-ch3-rank-bits=32 --with-device=ch3
用mpi编译时需加上-mhybrid(mpicc -mhybrid -o aa cpi.c)
【注】:(1)为了能使用mpif90,加上了--with-cross;
(2)为了mpi编译器有-mhybrid,加上了--enable-shared=no(LDFLAGS里加上-mhybrid)
这样用mpi编译时需就不用加上-mhybrid,(mpicc -show会显示)(mpicc -o aa cpi.c)
vim /home/xujb/cross_file
CROSS_F77_SIZEOF_INTEGER="4" CROSS_F77_SIZEOF_REAL="4" CROSS_F77_SIZEOF_DOUBLE_PRECISION="8" CROSS_F77_TRUE_VALUE="-1" CROSS_F77_FALSE_VALUE="0" CROSS_F90_ADDRESS_KIND="8" CROSS_F90_OFFSET_KIND="8" CROSS_F90_INTEGER_KIND="4" CROSS_F90_REAL_MODEL=" 6 , 37" CROSS_F90_DOUBLE_MODEL=" 15 , 307" CROSS_F90_INTEGER_MODEL=" 9" CROSS_F90_ALL_INTEGER_MODELS=" 2 , 1, 4 , 2, 9 , 4, 18 , 8," CROSS_F90_INTEGER_MODEL_MAP=" { 2 , 1 , 1 }, { 4 , 2 , 2 }, { 9 , 4 , 4 }, { 18 , 8 , 8 },"
sw_64-sunway-linux-gnu
./configure CC=sw9gcc CXX=sw9g++ FC=sw9gfortran F77=sw9gfortran \ CFLAGS="-mhybrid" CXXFLAGS="-mhybrid" FCFLAGS="-mhybrid" FFLAGS="-mhybrid" \ LDFLAGS="-L/usr/sw/lib -lswverbs_ft_perf -lswutils -L/usr/sw/lib -lswtm -lm -Wl,-zmuldefs -ldl -lrt -lpthread" \ --prefix=/usr/sw/mpi/mpi_20220301_SEA \ --build=x86_64-redhat-linux --host=sw_64sw6a-sunway-linux-gnu --target=sw_64sw6a-sunway-linux-gnu \ --with-cross=/home/xujb/cross_file \ --enable-shared=no --enable-static=yes \ --with-ch3-rank-bits=32 --with-device=ch3:sock \ --enable-threads=single --libdir=/usr/sw/mpi/mpi_20220301_SEA/lib/single_static
--with-device=ch3:swch
--enable-threads
大多数系统都是默认执行这个的,如果希望线程支持最好还是加上。注意gcc认为threads在系统不可用的时候,这句话退化成等于--enable-threads=single.也就是和--disable-threads是一样的。
--disable-threads
单线程模式。--libdir=/usr/sw/mpi/mpi_20220301_SEA/lib/single_static
补充crossfile文件
CROSS_SIZEOF_CHAR="1" CROSS_SIZEOF_SHORT="2" CROSS_SIZEOF_INT="4" CROSS_SIZEOF_LONG="8" CROSS_SIZEOF_LONG_LONG="8" CROSS_SIZEOF_FLOAT="4" CROSS_SIZEOF_DOUBLE="8" CROSS_SIZEOF_LONG_DOUBLE="8" CROSS_SIZEOF_VOID_P="8" CROSS_OFFSET_KIND="8" CROSS_ADDRESS_KIND="8" CROSS_BIGENDIAN=true FORT_INT8="1" CROSS_HAVE_LONG_LONG="1" CROSS_HAVE_LONG_DOUBLE="1" CROSS_INTEGER_KIND="4"
六、脚本源码文件
(1)taskset.sh
“$$”的意思是当前shell的pid,也就是脚本运行的当前进程号。 $0 当前脚本的执行名字 $n 当前脚本执行命令的第n个参数值,n = 1..9 $* 当前脚本执行命令的所有参数,此选项参数可超过9个 $# 当前脚本执行命令的输入参数个数,例如执行 ./test.sh aa bb cc ,则在 test.sh 里 $# 为 3 $! 上一个执行指令的PID(后台运行的最后一个进程的进程ID号) $- 显示shell使用的当前选项,与set命令功能相同 $@ 跟$*类似,但是可以当作数组用
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
下面对整个 /proc 目录作一个大略的介绍.
[number]
在 /proc 目录里, 每个正在运行的进程都有一个以该进程 ID 命名的子目录, 其下包括如下的目录和伪文件:
[number] /environ
该文件保存进程的环境变量, 各项之间以空字符分隔, 结尾也可能是一个空字符. 因此, 如果要输出进程 1 的环境变量, 你应该:
(cat /proc/1/environ; echo) | tr '\000' '\n'
#!/bin/bash mypid=$$ myrank_kv=`/bin/cat /proc/$mypid/environ|tr '\0' '\n'|/bin/grep PMI_RANK` myrank=`echo $myrank_kv|awk -F = '{print $2}'` #mycpunum=`expr $myrank % 4` #mycpuid=`expr $mycpunum \* 4` mycpuid=`expr $myrank \% 6` #echo "$PMI_RANK $myrank_kv $myrank $mycpunum $mycpuid" /bin/taskset -c $mycpuid $@ # ---------- 或者是下面这样 ---------- # mycpuid=`expr $PMI_RANK \% 6` /bin/taskset -c $mycpuid $@
(2)setcpu.c
#include<stdlib.h> #include<stdio.h> #include<sys/types.h> #include<sys/sysinfo.h> #include<unistd.h> #define __USE_GNU #include<sched.h> #include<ctype.h> #include<string.h> #include<pthread.h> #include "crts.h" #define THREAD_MAX_NUM 200 //1个CPU内的最多进程数 int num=0; //cpu中核数 void* threadFun(void* arg) //arg 传递线程标号(自己定义) { cpu_set_t mask; //CPU核的集合 cpu_set_t get; //获取在集合中的CPU int *a = (int *)arg; int i; printf("the thread is:%d\n",*a); //显示是第几个线程 CPU_ZERO(&mask); //置空 CPU_SET(*a,&mask); //设置亲和力值 if (sched_setaffinity(0, sizeof(mask), &mask) == -1)//设置线程CPU亲和力 { printf("warning: could not set CPU affinity, continuing...\n"); } CPU_ZERO(&get); if (sched_getaffinity(0, sizeof(get), &get) == -1)//获取线程CPU亲和力 { printf("warning: cound not get thread affinity, continuing...\n"); } for (i = 0; i < num; i++) { if (CPU_ISSET(i, &get))//判断线程与哪个CPU有亲和力 { printf("this thread %d is running processor : %d\n", i,i); } } return NULL; } int main(int argc, char* argv[]) { int tid[THREAD_MAX_NUM]; int i; pthread_t thread[THREAD_MAX_NUM]; num = sysconf(_SC_NPROCESSORS_CONF); //获取核数 int num_onln = sysconf(_SC_NPROCESSORS_ONLN); // 真正获取当前可用核数 if (num > THREAD_MAX_NUM) { printf("num of cores[%d] is bigger than THREAD_MAX_NUM[%d]!\n", num, THREAD_MAX_NUM); return -1; } char hostbuffer[256]; gethostname(hostbuffer, sizeof(hostbuffer)); printf("system has %i processor(s). actual processors number is : %d, cgn_id is :%d, hostnamne : %s\n", num, num_onln, current_array_id(), hostbuffer); printf("parent pid : %d, son pid: %d\n", getppid(), getpid()); // int pid = getpid(); // pthread_create(&getpid(),NULL, ); for(i=0;i<1;i++) { tid[i] = i; //每个线程必须有个tid[i] pthread_create(&thread[i],NULL,threadFun,(void*)&tid[i]); } for(i=0; i< 1; i++) { pthread_join(thread[i],NULL);//等待所有的线程结束,线程为死循环所以CTRL+C结束 } return 0; }
-
-