ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VS2019下MS-MPI环境配置与并行计算入门指南

VS2019下MS-MPI环境配置与并行计算入门指南 1. 为什么要在VS2019里搭MPI先把原理说透很多朋友第一次接触MPI是在并行计算课或者高性能计算相关的项目里老师留了个作业让在Windows上把MPI跑起来结果上网一搜全是Linux下mpich、OpenMPI的教程Visual Studio里配MPI的完整中文资料少得可怜。我就是从那个阶段过来的深知这玩意儿看着简单实际动手全是坑。先花几分钟把概念理清楚。MPI全称Message Passing Interface是一套消息传递接口标准它规定了并行进程之间怎么收发数据、怎么做集合通信、怎么同步。注意MPI本身不是一种编程语言也不是一个具体的软件它是一套“约定”。你就把它理解成快递行业的“面单规范”各家快递公司不同的MPI实现都遵循同一套面单格式只要按照格式填不管哪家快递都能送。在Windows平台上我们用的MPI实现是微软提供的MS-MPI。它分为两个独立安装包一个叫MS-MPI运行时MS-MPI Redistributable负责真正干活的底层库你编译好的exe在别人电脑上跑只需要装这个小的另一个叫MS-MPI SDKWindows SDK for MPI里面是头文件mpi.h和导入库msmpi.lib编译代码时必须要用到。装的时候很多教程说装第一个就行这是不对的——只装运行时VS里无法编译只装SDK程序跑不起来。两个都得装顺序无所谓保险起见建议先装运行时再装SDK。这套组合适合谁适合在Windows上用Visual Studio做并行计算开发的人尤其是高校学生、科研人员还有刚接触分布式计算想先在本机模拟多进程通信的开发者。它的核心价值在于你不需要真的有一台多节点集群在一台Windows电脑上就能通过多进程方式模拟并行执行调试MPI逻辑完全够用。本机跑通了后面再挪到Linux超算上MPI代码本身几乎不用改只是编译方式从MS-MPI换成mpich或者OpenMPI而已。我自己在配置过程中踩过的坑包括环境变量不生效、VS里找不到mpi.h、链接阶段报一堆LNK2019、x64和x86混用导致“拒绝访问”等等。这篇博文就把整个流程从零开始完整走一遍包括每个选项到底该填什么、为什么这么填以及遇到报错怎么排查照着一步步做保证能跑出你的第一个MPI程序。2. 下载与安装MS-MPI的两个组件缺一不可2.1 MS-MPI Redistributable和SDK到底有什么区别很多人第一次去微软官网下载时会被两个名称搞糊涂。简单说Redistributable是运行时相当于你电脑上装个“播放器”专门负责跑MPI程序SDK是开发工具包相当于“剪辑软件”负责帮你生成能播放的视频。开发时必须两个都有因为你既要编译需要SDK又要运行需要运行时。具体到文件上SDK安装后会提供这些关键东西头文件mpi.h——你的C/C代码里#include mpi.h就是找它导入库msmpi.lib——链接器用的告诉编译器MPI函数在哪工具程序mpiexec.exe——启动MPI多进程任务的命令全名Microsoft MPI Executable而Redistributable安装后提供的是msmpi.dll——这才是真正执行消息传递功能的动态链接库你的exe跑起来时会加载它。如果只装了SDK没装运行时你会遇到编译通过、运行报错“找不到MSMPI.dll”的情况后面会详细说这个问题。2.2 官方网站下载安装的完整步骤搜索“MS-MPI”或者直接进微软官网的Microsoft MPI页面下载时注意区分两个文件一般来说文件名类似msmpisetup.exe——运行时安装包体积一两兆msmpisdk.msi——SDK安装包体积十几兆下载完成后先双击msmpisetup.exe。安装界面很简单一路Next但默认安装路径建议保持不动因为后面VS配置时会用到标准路径虽然也可以自定义但对新手来说没必要徒增变数。默认安装位置是C:\Program Files\Microsoft MPI\。然后双击msmpisdk.msi同样是默认路径C:\Program Files\Microsoft SDKs\MPI\。SDK装完后建议你打开资源管理器确认一下这两个路径C:\Program Files\Microsoft MPI\Bin\目录下能看到mpiexec.exeC:\Program Files\Microsoft SDKs\MPI\Include\目录下能看到mpi.hC:\Program Files\Microsoft SDKs\MPI\Lib\x64\目录下能看到msmpi.lib看到这几个文件安装就算成功了。需要注意如果你的Windows系统是32位SDK里只有x64和x86两个子目录32位系统就选x86下的库文件但说实话现在64位系统已经很普及了后续教程默认按x64讲解。2.3 环境变量检查为什么有时候mpiexec命令无效理论上MS-MPI安装包会自己把mpiexec所在目录写进系统环境变量PATH里但实际中经常出现两种情况一种是安装时有杀毒软件拦截导致环境变量没写进去另一种是环境变量写了但当前已经打开的命令行窗口没刷新。安装完成后建议手动验证一次环境变量是否配置成功。打开命令行WinR输入cmd回车输入mpiexec -version如果看到类似Microsoft MPI Startup Program Version 10.x.x.x的输出说明环境变量没问题。如果提示“不是内部或外部命令”就说明PATH没生效需要手动把C:\Program Files\Microsoft MPI\Bin\添加进系统环境变量里。手动添加的方法不用多说右键“此电脑”-“属性”-“高级系统设置”-“环境变量”在“系统变量”里找到Path新增一条路径即可。这里有个细节添加完环境变量后之前已经打开的命令行窗口不会自动生效必须新开一个窗口。我遇到过好几次顺手就在旧窗口里敲命令结果半天没反应还以为自己配置错了。注意即使环境变量配置正确在VS的调试器里启动MPI程序时也不是直接运行exe而是需要通过mpiexec这个中间人启动。这个逻辑后面会详细解释。3. VS2019项目配置四个关键点一步都不能省3.1 创建项目时的架构选择x64还是x86打开VS2019选择“创建新项目”选“空项目”C输入项目名称比如MPIHelloWorld点击创建。项目建好后首先要做的第一件事不是写代码而是把解决方案平台从默认的x86改成x64。这里解释一下为什么。MS-MPI的库文件按64位和32位分开提供如果VS里选择了x8632位编译链接时就要用Lib\x86\下的msmpi.lib运行时也要找32位版本的msmpi.dll但绝大多数人安装MS-MPI时用的都是默认64位运行时如果你编译成32位程序运行时加载DLL就可能因为位数不匹配而失败。为避免不必要的麻烦直接统一用x64平台教程里所有配置都围绕x64展开。修改方法在VS顶部工具栏找到“解决方案平台”下拉框默认显示“x86”点开后选择“新建...”在弹出的对话框里选择“x64”确定即可。如果你在工具栏没看到这个下拉框可以在菜单栏“生成”-“配置管理器”里在“活动解决方案平台”处新建x64。3.2 包含目录配置让编译器找到mpi.h接下来配置头文件路径。右键项目名 - “属性”打开项目属性页。注意必须确保左上角“配置”和“平台”都是你实际使用的那一组比如“Debug”和“x64”否则你辛苦配置了半天编译时用的却是另一套配置自然找不到。在属性页左侧找到“配置属性”-“VC目录”右侧看到“包含目录”这一项点击下拉箭头选择“编辑...”在弹窗里添加一行C:\Program Files\Microsoft SDKs\MPI\Include这就是告诉编译器编译时去这个目录下找mpi.h。如果不设置这一项你写的#include mpi.h就会有红色波浪线编译时直接报“无法打开包括文件: mpi.h: No such file or directory”。同样在这个“VC目录”页面找到“库目录”添加C:\Program Files\Microsoft SDKs\MPI\Lib\x64这是告诉链接器链接时去这个目录下找msmpi.lib。如果不设置后面会报LNK1104“无法打开文件msmpi.lib”。3.3 附加依赖项链接器才知道要用哪个库到了这一步很多教程就结束了但这恰恰是默认配置容易漏掉的关键。只设置库目录还不够链接器默认不会自动连接msmpi.lib你得明确告诉它这个项目需要链接这个静态导入库。在属性页左侧找到“配置属性”-“链接器”-“输入”右侧找到“附加依赖项”点击下拉箭头选择“编辑...”在列表中添加msmpi.lib这一步做了之后链接器在链接阶段就会把这个库当作项目的一部分来处理。漏掉这一步的典型报错是LNK2019 无法解析的外部符号 MPI_Init LNK2019 无法解析的外部符号 MPI_Comm_size看到“无法解析的外部符号”千万不要慌九成都是这个原因函数声明编译器能找到mpi.h有但函数实现链接器不知道上哪找没连接msmpi.lib。很多人费了半天劲查代码、查函数名结果只是配置漏了一个附加依赖项。3.4 预处理器定义和代码生成两个容易忽略的小地方为了让你后面的编写过程少烦心建议同时把预处理器定义加上_CRT_SECURE_NO_WARNINGS。在属性页“配置属性”-“C/C”-“预处理器”-“预处理器定义”中把这一项加上作用是屏蔽掉VS对fopen、strcpy等传统C库函数的安全警告。MPI的示例代码里往往用到了这些函数不加这个定义虽然不影响编译但输出窗口会刷出一堆C4996警告很影响心情。另外在“配置属性”-“C/C”-“代码生成”-“运行库”处建议保持默认的“多线程调试(/MTd)”或者“多线程(/MT)”。这里解释一下为什么MS-MPI的导入库对应的运行时是静态链接方式还是动态方式和你的选择要尽量一致如果这里选了/MT而MS-MPI本身是动态库方式理论上也没问题因为msmpi.dll是独立加载的并不依赖VC运行库的多线程模式但根据我的实际经验Debug模式下用/MTd、Release模式下用/MT最省心不容易出现莫名其妙的内存分配交叉问题。提示还有一个很关键但容易被忽略的配置——字符集。VS2019默认的字符集如果是Unicode而你的MPI程序里用了char*字符串有些函数调用可能需要显式转换。最省事的方法是保持默认写代码时注意用char*而不是TCHAR*后面的示例代码也会按这个风格来。配置完成后点“确定”保存属性设置。这时候你可能会想每次新建项目都要重复配置这么多东西能不能一次搞定可以VS2019支持属性表导出配置完一次后右键项目-“导出属性表”下次新建项目导入即可。但对于新手来说我建议前几个项目手动配配得多了才能理解每一行配置的含义出错了也知道去哪里排查。3.5 配置检查清单把上面四步汇总成一个自查清单配完对照检查配置项位置填写内容包含目录VC目录 - 包含目录C:\Program Files\Microsoft SDKs\MPI\Include库目录VC目录 - 库目录C:\Program Files\Microsoft SDKs\MPI\Lib\x64附加依赖项链接器 - 输入 - 附加依赖项msmpi.lib预处理器定义C/C - 预处理器 - 预处理器定义添加_CRT_SECURE_NO_WARNINGS平台解决方案平台x644. 写第一个MPI程序并跑通从编译到运行4.1 完整示例代码Hello World的并行版本配置工作做完现在写代码验证环境是否真正配置成功。在解决方案资源管理器里右键“源文件”-“添加”-“新建项”选“C文件(.cpp)”命名为main.cpp把下面的代码敲进去#include mpi.h #include stdio.h #include string.h int main(int argc, char* argv[]) { int rank, size, namelen; char processor_name[MPI_MAX_PROCESSOR_NAME]; MPI_Init(argc, argv); MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); MPI_Get_processor_name(processor_name, namelen); printf(Hello from process %d of %d on %s\n, rank, size, processor_name); MPI_Finalize(); return 0; }这段代码的功能是每个MPI进程都有自己独立的编号rankMPI_Comm_rank拿到当前进程的编号MPI_Comm_size拿到总进程数MPI_Get_processor_name拿到当前运行机器的主机名。最终每个进程打印一句包含自己编号和进程总数的消息。这里稍微解释一下初学者最常见的疑问MPI_Init(argc, argv)为什么传的是指针因为MPI需要从命令行参数里解析一些配置选项比如主机列表、进程数等传指针进去MPI初始化时可以修改这些参数也算是老接口设计上的一种约定。你不需要深入理解它的内部实现只要记住MPI程序第一个调用必须是MPI_Init最后一个调用必须是MPI_Finalize中间才是你自己的并行逻辑。如果违背这个顺序MPI会报MPI_Init has already been invoked之类的错误。4.2 编译并运行理解mpiexec的启动逻辑代码写好后直接点击“生成”-“生成解决方案”如果前面的配置都正确输出窗口应该显示“生成成功”并生成x64\Debug\MPIHelloWorld.exe。现在关键来了。千万不要直接双击exe运行那样只会启动一个进程完全体现不出MPI的价值。正确的方式是通过mpiexec命令启动多个进程。这个设计可以类比一个电影导演exe就像演员mpiexec就像导演导演负责协调多个演员在正确的时间登台。先打开命令行窗口切换到exe所在的目录cd /d D:\你的项目路径\x64\Debug mpiexec -n 4 MPIHelloWorld.exe-n 4表示启动4个MPI进程。执行后你会看到类似这样的输出Hello from process 1 of 4 on DESKTOP-XXXXXXX Hello from process 0 of 4 on DESKTOP-XXXXXXX Hello from process 3 of 4 on DESKTOP-XXXXXXX Hello from process 2 of 4 on DESKTOP-XXXXXXX注意这个现象进程0、1、2、3的打印顺序是乱的每次运行可能都不一样。这不是bug而是并行程序的典型特征——多个进程同时执行执行速度有微小差异谁先走到printf就谁先打印。看到这个乱序输出恰恰说明你的MPI环境配置成功了多个进程确实在并行运行。4.3 在VS里启动多进程调试VS调试器的MPI插件方式有的朋友会觉得每次都在命令行里敲mpiexec太麻烦想直接在VS里按F5调试。VS2019本身不直接支持MPI调试但可以通过配置调试命令来间接实现。右键项目-“属性”左侧“调试”选择“命令行参数”填上-n 4 $(TargetPath)“命令”处填入C:\Program Files\Microsoft MPI\Bin\mpiexec.exe这样配置后按F5时VS会通过mpiexec来启动你的exe并且可以正常打断点、单步调试。调试多进程时有个小技巧在VS的“调试”-“窗口”-“进程”面板里可以看到所有MPI子进程想在某一个进程里断住可以右键该进程选择“中断”这样不会影响其他进程的运行。这个技巧在实际调试P2P通信或集合通信逻辑时特别有用比在代码里乱加printf高效得多。不过有一个坑得提前说VS调试MPI进程时如果程序里涉及到网络通信比如跨机器运行因为你本机的MPI环境只模拟了单机多进程通信走的是共享内存回环速度很快但你在VS里看到的“线程”视图可能有点混乱——多个进程、每个进程又有主线程鼠标点起来容易乱。不用慌只要逻辑层是对的进程层面的表现乱一点是正常的。4.4 验证安装路径是否影响运行时有些读者可能安装MS-MPI时改过路径或者系统比较特殊比如装了多个版本的MS-MPI运行时会出现mpiexec能找到但exe启动时加载不到msmpi.dll的情况。这里给一个排查思路打开命令行执行where msmpi.dll where mpiexec如果where命令能列出路径比如C:\Program Files\Microsoft MPI\Bin\msmpi.dll说明运行时文件确实在。如果这个命令找不到多半是环境变量被改坏了或者安装时被杀毒软件拦截了一部分文件。最稳妥的解决办法是卸载干净后重新安装一遍这一次先退出杀毒软件再执行安装包。另外如果同一个系统里装了多套MS-MPI比如先装过旧版又装新版建议只保留最新版。旧版残留的msmpi.dll如果留在C:\Windows\System32里可能干扰新版运行。检查一下你的C:\Windows\System32下是否有msmpi.dll有的话确认它和你编译时用的SDK版本是否一致。版本不一致的典型表现就是编译时完全正常运行时莫名报错或者卡死。5. 常见问题与排查技巧实录5.1 编译阶段的报错header和linker问题配置过程中最常遇到的是编译阶段报错我把典型情况整理成表格方便你对照排查报错信息含义解决方案Error C1083: 无法打开包括文件: mpi.h编译器找不到头文件检查包含目录是否填写正确确认路径是...\Include而不是...\Include\漏了一层Error LNK1104: 无法打开文件 msmpi.lib链接器找不到库文件检查库目录是否填写为...\Lib\x64注意必须是x64或与你的编译平台一致LNK2019: 无法解析的外部符号 MPI_Init链接器没连接MPI库检查“附加依赖项”是否添加了msmpi.libLNK2038: 运行时库不匹配Debug/Release和/MT、/MD配置冲突统一“代码生成”-“运行库”为/MTdDebug或/MTRelease不要混用我印象最深的一次是帮同学排查LNK2019他坚持说自己配置了附加依赖项我远程一看明明他改的是“配置属性”-“VC目录”-“库目录”而“链接器”-“输入”-“附加依赖项”那一栏还是空的。这两个地方长得像但作用完全不同库目录是告诉链接器“你上哪找库文件”附加依赖项是告诉链接器“你要用哪个具体的库”。少了后者链接器就算找到了也不能主动去连。这个坑真的很典型。5.2 运行阶段的报错dll加载和启动问题编译成功只是第一步运行阶段的问题往往更让人抓狂。以下是我见过的高频运行时报错及解决方法报错信息含义解决方案The code execution cannot proceed because MSMPI.dll was not found系统找不到MPI运行时DLL安装MS-MPI Redistributable确认msmpi.dll所在目录在PATH中Unable to allocate 1048576 bytes of memory内存分配失败多半是杀毒软件或UAC权限限制以管理员身份运行命令行mpiexec: The parameter -n is invalidmpiexec参数写错确认是-n 4不是-n4中间有空格程序可以运行但卡住不退出某个进程在MPI_Finalize前还在等待通信检查MPI_Init和MPI_Finalize是否成对出现中间是否有阻塞式通信没配对关于“卡住不退出”我再说得细一点。MPI的集合通信操作比如MPI_Barrier、MPI_Allreduce是同步行为意思是所有进程都必须执行到这个点才会一起继续前进。如果你的程序里4个进程其中3个执行到了MPI_Barrier但第4个进程还在处理别的逻辑没调用这个函数前3个就会一直等着程序看起来就像“死机”了。这种问题在逻辑编写错误时尤其常见对应到排查方式就是检查每个进程都执行了相同的集合通信调用。5.3 环境冲突问题MS-MPI版本混乱时的处理方案多版本MS-MPI共存导致的冲突虽然不常遇到但一旦遇到就很头疼。正常情况下MS-MPI的安装包会自动覆盖旧版本但如果你以前手动拷贝过dll到系统目录就可能出现版本错位。我的建议是先卸载所有MS-MPI相关组件重启电脑然后重新安装一次最新版的运行时和SDK。安装完成后打开C:\Program Files\Microsoft MPI\Bin右键msmpi.dll在“详细信息”标签页里查看“产品版本”再到C:\Windows\System32下查看同名文件的“产品版本”两个版本应该一致。如果不一致以C:\Program Files\Microsoft MPI\Bin下的版本为准删掉System32下那个旧的或者用新版覆盖过去。为什么System32下可能会有一个msmpi.dll因为部分老版本安装器会把DLL放到系统目录后来的版本改了策略放进自己目录。如果代码开发机器上两个版本并存且环境变量指向不明确加载到的DLL就可能不是你想用的那个。这个问题在多环境开发比如你既装了MS-MPI又装了其他MPI实现时尤其明显。5.4 AUTO排查法三件事按顺序做最后分享一个我自己的排查方法按顺序检查基本能解决95%的配置问题看配置页平台打开项目属性左上角是不是Debug x64如果这里显示的是Win32你配置的那一堆全都没生效。这是新手最容易忽略的一点因为VS新建项目默认是Win32平台你在属性页里改了内容但当时的平台可能选的是Win32后来切成x64相当于白改了。看mpiexec是否可用命令行运行mpiexec -version报错就先解决环境变量不报错再下一步。这一步排除了运行时的路径问题。看程序退出码命令行运行mpiexec -n 2 yourApp.exe如果返回退出码是0说明程序正常结束如果是非0去MS-MPI的官方文档查退出码含义。我自己遇到最多的是退出码0xC0000005也就是访问违规通常是指针操作越界和MPI通信不匹配导致的。6. 从单机到多机配置好环境之后还能做什么当你成功跑通上面的Hello World本机MPI环境算是彻底配好了。这时候你手上拿的已经不只是一个“能吃灰的环境”而是一套可以继续深入学习的并行计算工具箱。第一个方向是尝试更多的MPI通信API。比如MPI_Send和MPI_Recv做点对点通信自己模拟一个“进程间互发消息”的完整示例再试试MPI_Bcast做广播MPI_Reduce做归约理解不同通信模式在并行算法里怎么用。我在初学的时候觉得最难理解的是MPI_Recv的status参数它告诉我们实际收到了多少数据、来自哪个进程。多写几个收发程序用printf打印出来慢慢观察很快就清楚了。第二个方向是做个小型的并行计算demo。比如写一个计算数组求和的程序把数组拆分到4个进程每个进程算一部分和最后用MPI_Reduce合并结果。这样你能直观感受到并行计算的精髓大任务切分成小任务并行执行最后汇总。还可以比较一下在不同进程数下的运行时间感受一下并行效率的变化。不过要提醒一句并行不是万能的任务本身太小的话进程间通信的开销可能超过计算省下的时间跑出来结果反而比单进程慢——这个现象叫“并行开销”也是学习并行计算的必修课。第三个方向是了解如何把MPI代码部署到集群上。本机配好的MS-MPI环境主要用于学习和开发真正跑大规模并行任务还是要在Linux超算或者集群上做。好在MPI标准是跨平台一致的你写的MPI_Init、MPI_Send、MPI_Recv这些程序逻辑在Linux上用mpich或者OpenMPI重新编译就能跑只需要改编译命令程序逻辑基本不用动。所以现在在本机上把逻辑吃透以后切到集群环境会非常顺利。7. 个人经验与最终提醒最后分享几个我自己反复体会出来的经验。第一MPI配置不是一次性的换电脑、换VS版本、换项目都可能要重来。我之前在VS2019上配好MPI后来换到VS2022发现属性页的界面和默认值有细微差别摸索了一会儿才重新配好。所以建议把这篇教程里的关键配置截图或者记成笔记放在一个方便查找的地方下次需要时直接照做效率高很多。第二遇到报错先分清是编译期还是运行期。编译期的错检查包含目录、库目录、附加依赖项这三个配置运行期的错检查运行时是否安装、环境变量是否正确、进程数参数写没写对。不分阶段地瞎猜只会浪费大量时间。线上各种求助帖里最常见的就是“我把附加依赖项加了但还是报LNK2019”结果一看库目录是空的——所以排查时花一分钟检查配置项的完整性比反复改代码要快得多。第三对新手来说环境配置的挫败感远远大于编程本身这是非常正常的不要因此怀疑自己。我第一次配MPI的教训是认真看了三篇教程每篇都只说了一部分最后把每一篇的遗漏点拼起来才完整跑通。这就是为什么我写这篇文章的时候刻意把每一步都拆得很细连“附加依赖项”这种看起来不起眼但实际上决定成败的步骤都拿出来单独写一节——因为我踩过这个坑知道它有多坑。这篇文章已经讲完了从原理到安装、从配置到运行的完整流程。希望你能Hands-on地走一遍把环境配好把第一个MPI程序跑起来。如果在实际操作中遇到这篇文章没覆盖到的问题按第三节的排查顺序走一遍一切都会豁然开朗。
返回列表