Linux下MPI的安装与vscode的配置 – 码途拾遗 您所在的位置:网站首页 如何退出阿里郎电脑端 Linux下MPI的安装与vscode的配置 – 码途拾遗

Linux下MPI的安装与vscode的配置 – 码途拾遗

2023-09-05 22:12| 来源: 网络整理| 查看: 265

零、前言

近期由于一些原因接触到了并行计算,对于这个陌生的领域我最先接触到的是MPI框架。MPI(Message Passing Interface),可以理解为是一种独立于语言的信息传递标准。目前它有两种具体的实现OpenMPI和MPICH,也就是说如果我们要使用MPI标准进行并行计算,就需要安装OpenMPI或MPICH库。本文以MPICH为例,在ubantu中安装MPI的环境,并对vscode进行配置。

一、MPI安装 1.1 前置准备

安装 mpich 之前需要安装好相应的编译器,可以通过查看是否安装了

$ gcc --version $ fortran --version $ gfortran --version

image-20220320165141580

如果没有安装则使用 sudo apt-get install gcc 安装即可(gcc替换成你没有的编译器)。

1.2 下载MPI

可以去这里下载一个MPI的程序包,选择你要下载的版本即可,其中Platform要选择MPICH的。

image-20220320165406607

下好了之后将其放到你要安装的目录下,强烈建议在home目录下建一个新的文件夹比如mpi来放置

1.3 安装

将下载的安装包进行解压,可以在窗口中选中右键解压,也可以 cd 到目录下用 tar xzf +文件名 来解压。

然后配置安装路径,cd到解压的文件夹,我的解压文件夹名称为mpich-3.4.3,所以我先cd mpich-3.4.3 ,然后输入./configure -prefix=/home/[username]/mpi ,其中 -prefix= 后写的是你的mpich的安装路径。

然后进行编译 make ,这一步很久(我大概用了十几分钟),需要耐心等待。

然后进行安装 make install 。

安装完成后添加环境变量,先输入sudo gedit ~/.bashrc ,然后会要求你输入密码,输入后会弹出一个文本框,输入下列路径:

export MPIPATH=/home/fang/mpi export MPIPATHBIN=$MPIPATH/bin export MPIPATHINCLUDE=$MPIPATH/include export MPIPATHLIB=$MPIPATH/lib export MPIPATHSHARE=$MPIPATH/share export PATH=$PATH:$MPIPATHBIN:$MPIPATHINCLUDE:$MPIPATHLIB:$MPIPATHSHARE

image-20220320171145276

注意: 第一行的 MPIPATH 需要写你安装的MPI的那个文件夹,其他不用改动

然后在终端中输入 source .bashrc 激活环境变量

1.4 测试

首先输入 which mpicc 可以查看你的mpich的安装路径。

然后打开终端cd进入你所下载的压缩包的解压文件夹,该路径下有个 example 文件夹,里面是mpich官方的示例代码,终端中输入:

mpirun -np 10 ./examples/cpi mpiexec -n 4 ./examples/cpi

image-20220320171754484

就完成了!

二、运行MPICH 3.1 命令行大法

如果用C++编写则用第一条,如果用C编写则用第二条,其中xxx是你要编译的文件名,yyy是你编译完成后生成的exe文件的文件名

mpic++ xxx.cpp -o yyy mpigcc xxx.c -o yyy

然后运行可执行文件,需要先cd到可执行文件的路径下,yyy 是你的可执行文件夹名字,千万不能漏掉 ./, 前面的参数 4 表示分配4个进程并行运行

mpirun -np 4 ./yyy 3.2 vscode配置

使用code runner插件运行,进入插件设置页,然后点击 在settings.json中编辑,自动进入settings.json 文件

image-20220320172544098

文件结构如下所示:

image-20220320172707492

可以复制我的配置:

{ "code-runner.runInTerminal": true, "files.autoSave": "afterDelay", "code-runner.executorMap": { "javascript": "node", "java": "cd $dir && javac $fileName && java $fileNameWithoutExt", "c": "cd $dir && gcc $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt", //"cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt", "cpp" : "cd $dir && mpic++ $fileName -o /home/fang/code/papercode/exe/$fileNameWithoutExt &&cd $dir && mpirun -np 4 /home/fang/code/papercode/exe/$fileNameWithoutExt", "objective-c": "cd $dir && gcc -framework Cocoa $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt", "php": "php", "python": "python -u", "perl": "perl", "perl6": "perl6", "ruby": "ruby", "go": "go run", "lua": "lua", "groovy": "groovy", "powershell": "powershell -ExecutionPolicy ByPass -File", "bat": "cmd /c", "shellscript": "bash", "fsharp": "fsi", "csharp": "scriptcs", "vbscript": "cscript //Nologo", "typescript": "ts-node", "coffeescript": "coffee", "scala": "scala", "swift": "swift", "julia": "julia", "crystal": "crystal", "ocaml": "ocaml", "r": "Rscript", "applescript": "osascript", "clojure": "lein exec", "haxe": "haxe --cwd $dirWithoutTrailingSlash --run $fileNameWithoutExt", "rust": "cd $dir && rustc $fileName && $dir$fileNameWithoutExt", "racket": "racket", "scheme": "csi -script", "ahk": "autohotkey", "autoit": "autoit3", "dart": "dart", "pascal": "cd $dir && fpc $fileName && $dir$fileNameWithoutExt", "d": "cd $dir && dmd $fileName && $dir$fileNameWithoutExt", "haskell": "runhaskell", "nim": "nim compile --verbosity:0 --hints:off --run", "lisp": "sbcl --script", "kit": "kitc --run", "v": "v run", "sass": "sass --style expanded", "scss": "scss --style expanded", "less": "cd $dir && lessc $fileName $fileNameWithoutExt.css", "FortranFreeForm": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt", "fortran-modern": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt", "fortran_fixed-form": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt", "fortran": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt" }, "window.zoomLevel": 2, "explorer.confirmDelete": false }

其中的cpp表示当运行的文件为c++时使用的命令,你也可以自己配置,可用的参数如下:

$workspaceRoot $dir $dirWithoutTrailingSlash $fullFileName $fileName $fileNameWithoutExt

以我所配置的mpich的运行方式为例进行解释:

"cpp" : "cd $dir && mpic++ $fileName -o /home/fang/code/papercode/exe/$fileNameWithoutExt &&cd $dir && mpirun -np 4 /home/fang/code/papercode/exe/$fileNameWithoutExt" 首先 cd 到 $dir$ 表示进入当前文件所在路径 && 表示并的关系,即只有前面的命令运行成功后才运行后面的命令。 然后是 mpic++ $fileName -o /home/fang/code/papercode/exe/$fileNameWithoutExt,这里的 $fileName 是当前需要运行的代码文件名称,$fileNameWithoutExt表示不带后缀的文件名称,这一行的命令意思是将文件使用MPICH进行编译,然后存放到 /home/fang/code/papercode/exe/路径下,编译出来的文件名为 $fileNameWithoutExt 最后是mpirun -np 4 /home/fang/code/papercode/exe/$fileNameWithoutExt 就是使用MPICH运行可执行文件,不在解释。 三、MPI编程框架 1.MPI_Init

任何MPI程序都应该首先调用该函数。 此函数不必深究,只需在MPI程序开始时调用即可(必须保证程序中第一个调用的MPI函数是这个函数)。

MPI_Init(&argc, &argv)

Fortran版本调用时不用加任何参数,而C和C++需要将main函数里的两个参数传进去,因此在写main函数的主程序时,应该加上这两个形参。

int main(int *argc,char* argv[]) { MPI_Init(&argc,&argv); } 2.MPI_Finalize

任何MPI程序结束时,都需要调用该函数。 该函数同第一个函数,都不必深究,只需要求格式去写即可。

MPI_Finalize() 3.MPI_COMM_RANK int MPI_Comm_Rank(MPI_Comm comm, int *rank)

该函数是获得当前进程的进程标识,如进程0在执行该函数时,可以获得返回值0(即rank = 0)。可以看出该函数接口有两个参数,前者为进程所在的通信域,后者为返回的进程号。通信域可以理解为给进程分组,比如有0-5这六个进程。可以通过定义通信域,来将比如 [0,1,5] 这三个进程分为一组,这样就可以针对该组进行“组”操作,MPI_COMM_WORLD是MPI已经预定义好的通信域,是一个包含所有进程的通信域,目前只需要用该通信域即可。

在调用该函数时,需要先定义一个整型变量如myid,不需要赋值。将该变量传入函数中,会将该进程号存入myid变量中并返回。

4.MPI_COMM_SIZE

该函数是获取该通信域内的总进程数,如果通信域为MP_COMM_WORLD,即获取总进程数,使用方法和MPI_COMM_RANK相近。

MPI_COMM_SIZE(comm, size) int MPI_Comm_Size(MPI_Comm, int *size) 5.MPI_SEND

该函数为发送函数,用于进程间发送消息,如进程0计算得到的结果A,需要传给进程1,就需要调用该函数。

call MPI_SEND(buf, count, datatype, dest, tag, comm) int MPI_Send(type* buf, int count, MPI_Datatype, int dest, int tag, MPI_Comm comm)

该函数参数过多,不过这些参数都很有必要存在。

这些参数均为传入的参数,其中buf为你需要传递的数据的起始地址,比如你要传递一个数组A,长度是5,则buf为数组A的首地址。count即为长度,从首地址之后count个变量。datatype为变量类型,注意该位置的变量类型是MPI预定义的变量类型,比如需要传递的是C++的int型,则在此处需要传入的参数是MPI_INT,其余同理。dest为接收的进程号,即被传递信息进程的进程号。tag为信息标志,同为整型变量,发送和接收需要tag一致,这将可以区分同一目的地的不同消息。比如进程0给进程1分别发送了数据A和数据B,tag可分别定义成0和1,这样在进程1接收时同样设置tag0和1去接收,避免接收混乱。

6.MPI_RECV

该函数为MPI的接收函数,需要和MPI_SEND成对出现。

call MPI_RECV(buf, count, datatype, source, tag, comm,status) int MPI_Recv(type* buf, int count, MPI_Datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

参数和MPI_SEND大体相同,不同的是source这一参数,这一参数标明从哪个进程接收消息。最后多一个用于返回状态信息的参数status。

在C和C++中,status的变量类型为MPI_Status,分别有三个域,可以通过status.MPI_SOURCE,status.MPI_TAG和status.MPI_ERROR的方式调用这三个信息。这三个信息分别返回的值是所收到数据发送源的进程号,该消息的tag值和接收操作的错误代码。

SEND和RECV需要成对出现,若两进程需要相互发送消息时,对调用的顺序也有要求,不然可能会出现死锁或内存溢出等比较严重的问题。

7. MPI_Barrier

该函数为一个阻塞函数

MPI_Barrier(MPI_Comm comm);

填入的参数为通信域,当进程执行该函数并且属于该通信域时,则停止执行进入等待状态,当该通信域的所有进程都执行到该函数后才继续往下进行。

7. 例子 int main(int argc, char** argv) { MPI_Init(NULL, NULL); // MPI启动 一直到Finalize之间的都会并行执行 int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); // 获取当前进程id if (rank == 0) { ...... // 对0号进程进行操作 }else if(rank == 1) { ...... // 对1号进程进行操作 } MPI_Barrier(MPI_COMM_WORLD); //等待函数,所有进程都调用这个函数后才继续往下运行 MPI_Comm_rank(MPI_COMM_WORLD, &rank); printf("rank%d\n", rank); if(rank == 1){ ...... } MPI_Finalize();// MPI结束 return 0; } 四、总结

这次配置MPICH的过程还是收获颇丰的,第一次领略到了用Linux安装环境的快捷与舒适,还了解vscode的很多配置原理(之前都是无脑配置的),最后还入门了一种全新的编程方式,并行计算。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有