十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Windows下搭建MPI并行计算环境:MS-MPI安装、配置与避坑指南

Windows下搭建MPI并行计算环境:MS-MPI安装、配置与避坑指南 干并行计算这行Windows下搭MPI环境这事我前前后后折腾过好几轮。上学那会儿实验室全是LinuxMPI_CH 装起来两条命令的事压根没觉得是问题。结果到了实际项目里对面团队拿的全是Windows工作站数据预处理、仿真程序全在这上面跑让我给配一套能用的MPI环境这才发现坑比想象中多得多。后来把MS-MPI、MinGW移植版、WSL几条路都试了一遍才摸出一套稳当的流程。这篇文章就把我能跑通的方案、踩过的坑、以及背后为什么要这么选的原因一次说清楚。这篇文章适合谁刚接触并行计算、手头只有Windows机器但想跑MPI程序的学生以及公司里需要在Windows服务器上部署并行任务、又不想开Linux虚拟机的工程师。我会从方案选型讲起到安装配置、编写第一个程序、编译运行、再到常见问题排查给出完整可复现的路径。1. 方案选型Windows下跑MPI到底该选哪套1.1 为什么Windows下搭MPI会让人头疼先说清楚问题出在哪。MPI本身是一套消息传递接口标准不是某个具体的软件。Linux生态里OpenMPI和MPICH两大实现随便选包管理器一行命令装好编译器默认能找到头文件和库文件体验很顺畅。Windows这边情况就不同了微软自己维护了一套基于MPICH的MS-MPI但它的安装方式、编译链接方式跟Linux下的习惯差别很大很多教程默认读者用Linux照着敲命令在Windows下全废。再加上Windows下的C/C编译器生态比较特殊主流的MSVC跟Linux下的gcc/clang在命令行参数、库文件格式上完全不同。很多人卡在第0步明明MPI装好了一编译就报找不到mpi.h或者一堆无法解析的外部符号其实多半是编译环境跟MPI库的搭配出了问题。1.2 三条可行路线的对比我实际试下来Windows下跑MPI基本有三条路各有优劣方案核心组件编译方式适合场景维护成本方案AMS-MPI MSVCmsmpisdk Visual Studio Build Toolscl /EHsc 或VS工程纯Windows环境、需要跟Visual Studio协同低官方SDK更新及时方案BMPI For Windows(gcc移植版)MinGW-w64 MS-MPI运行时gcc msmpi库习惯gcc命令行的开发者中库依赖需要手动处理方案CWSL OpenMPI/MPICHWindows Subsystem for Linuxgcc/mpicc需要跟Linux环境保持一致低但跨系统文件访问稍麻烦先说结论如果你的目标是在Windows上把MPI程序跑起来、跟Visual Studio配合做开发调试方案A是最省心的。微软官方维护的MS-MPI跟Windows系统集成度最高运行时不依赖额外DLL部署到别的Windows机器上也比较干净。方案B适合那些习惯了gcc编译参数、又不想碰WSL的朋友但安装配置步骤反而更琐碎。方案C其实很讨巧等于在Windows里开了个Linux环境能直接复用巨量Linux教程里的命令但如果你需要调用的Windows原生程序或者COM组件WSL里会非常别扭。1.3 我为什么最终选了MS-MPI MSVC我个人最终长期用的是MS-MPI加Visual Studio Build Tools这条路。理由有三第一库的完整度最高。MS-MPI的SDK里包含了mpi.h头文件、x86和x64两套导入库、以及丰富的C/C示例代码官方文档写得也很清楚出问题能查到的地方多。第二部署目标环境干净。MS-MPI的运行时安装包很小也就几兆装到目标机器上不需要额外配环境变量就能直接跑mpiexec这对后面把程序交付给别人用很重要。第三Visual Studio的调试体验没法替代。并行程序最怕找bugVS的并行堆栈调试器能直接显示每个进程的调用栈和通信状态这条对实际开发效率提升不是一点半点。后面我会详细讲怎么从命令行编译运行、怎么在VS里配置两条路都给出来。2. 详细安装与配置MS-MPI的完整落地步骤2.1 下载与安装两个安装包一个都不能少这是第一步、也是很多人会踩坑的地方。MS-MPI的安装包分为两个一个是运行时安装包msmpisetup.exe另一个是SDK开发包msmpisdk.msi。运行时是每台需要执行MPI程序的机器都要装的SDK则是开发机用来拿头文件和导入库的。我第一次装的时候只装了SDK结果一运行程序就报错说找不到MS-MPI运行时回头又补装了一遍运行时才解决。所以建议你在开发机上两个都装顺序无所谓但别漏。到微软官网搜索“MS-MPI”进入官方下载页认准文件名里有msmpisetup和msmpisdk的两个链接下载对应版本即可。目前MS-MPI已经更新到10.x版本功能上比较稳定管理任务调度和安全机制也在持续改进。装SDK时注意安装路径。默认会装到C:\Program Files (x86)\Microsoft SDKs\MPI后面配置编译环境会用到这个路径。如果你改了安装位置记得记下来。2.2 安装Visual Studio Build Tools或完整版VS编译MPI程序需要C/C编译器。如果你电脑上已经有Visual Studio确保安装了“使用C的桌面开发”工作负载即可。如果没有VS也不想装那么大个IDE直接安装Visual Studio Build Tools就行这个工具安装时也能选择C编译器组件。去Visual Studio官网下载Build Tools安装器启动后勾选“使用C的桌面开发”右侧默认组件保持全选然后点安装。这个过程需要下载几个GB的内容建议预留充足时间和磁盘空间。装完后你会在开始菜单找到“x64 Native Tools Command Prompt for VS 2022”之类的快捷方式这个就是后面命令行编译要用的环境。这里解释一下为什么要用这个特殊的命令行窗口MSVC的编译器cl.exe依赖一系列环境变量来判断头文件搜索路径、库搜索路径直接用普通的cmd窗口打开cl会报“不是内部或外部命令”。VS提供的这个快捷方式启动时会自动执行vcvars64.bat脚本把编译器路径注入当前会话相当于把环境准备好的壳递给你的每个命令。2.3 配置环境变量让mpiexec随时可用MS-MPI的运行时在装完msmpisetup.exe之后一般会自动把mpiexec加入系统PATH。但为了避免某些环境下自动配置失败我建议手动检查一遍。右键“此电脑” - “属性” - “高级系统设置” - “环境变量”在“系统变量”里找到Path点击编辑看有没有以下两条C:\Program Files\Microsoft MPI\Bin\C:\Program Files (x86)\Microsoft SDKs\MPI\Bin\如果存在没有的话就新增进去。第一条是mpiexec和运行时DLL所在的目录第二条是SDK自带的一些辅助工具目录。配置好后新开的cmd窗口里执行mpiexec -help能看到一大串参数说明就说明运行时环境OK了。注意改完环境变量后已经打开的cmd窗口不会自动刷新PATH必须新开一个窗口再验证。2.4 检查编译环境先确认cl能正常工作在开始编译MPI程序之前先确认编译器环境没问题。打开“x64 Native Tools Command Prompt for VS 2022”执行cl如果输出里有类似“用于 x64 的 Microsoft (R) C/C 优化编译器”的版本信息就说明编译器可用。如果提示找不到命令检查一下是不是快捷方式用错了或者安装时没有勾选对应组件。再顺便确认一下MPI头文件和库文件的位置dir C:\Program Files (x86)\Microsoft SDKs\MPI\Include\mpi.h dir C:\Program Files (x86)\Microsoft SDKs\MPI\Lib\x64\msmpi.lib两个文件都存在编译环境的准备就完成了。3. 第一个MPI程序从源码到运行3.1 写一个能验证“多进程通信”的示例环境搭好了接下来就是跑通一个真实程序。我建议第一个程序不要写hello world太简单验证不了通信功能。这里给一个经典的“分布式求和”示例每个MPI进程计算自己负责的那一段数字之和最后通过归约操作把部分和汇总到0号进程。#include stdio.h #include mpi.h #define ARRAY_SIZE 1000 int main(int argc, char *argv[]) { int rank, size; int i; int local_sum 0; int global_sum 0; int data[ARRAY_SIZE]; MPI_Init(argc, argv); MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); // 初始化数据全部填1方便核对结果 if (rank 0) { for (i 0; i ARRAY_SIZE; i) { data[i] 1; } } // 广播数据到所有进程 MPI_Bcast(data, ARRAY_SIZE, MPI_INT, 0, MPI_COMM_WORLD); // 每个进程计算自己负责的那一段的和 int chunk ARRAY_SIZE / size; int start rank * chunk; int end (rank size - 1) ? ARRAY_SIZE : start chunk; for (i start; i end; i) { local_sum data[i]; } printf(进程 %d/%d 计算了 [%d, %d) 的和局部结果 %d\n, rank, size, start, end, local_sum); // 归约操作把所有进程的局部和加起来最终汇总到0号进程 MPI_Reduce(local_sum, global_sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); if (rank 0) { printf(全局求和结果 %d预期结果 %d\n, global_sum, ARRAY_SIZE); } MPI_Finalize(); return 0; }这段程序里用了三个关键的MPI调用我稍微解释一下原理MPI_Init和MPI_Finalize是MPI程序的入口和出口每个进程都必须调用成对出现。MPI_Comm_rank获取当前进程在通信域里的编号从0开始MPI_Comm_size获取总进程数。MPI_Bcast是一对多的广播操作0号进程把整个数组发到所有其他进程保证每个进程拿到的数据一致。MPI_Reduce是多对一的归约操作所有进程的local_sum通过MPI_SUM操作累加最终结果只存在于0号进程。这个程序完整地演示了MPI中最核心的两个通信模式集体通信广播和归约和进程间的分工协同是理解MPI编程模型很好的起点。3.2 命令行编译cl命令的完整写法把上面的代码保存为test_mpi.c然后在x64命令行窗口里执行编译命令cl /EHsc test_mpi.c /I C:\Program Files (x86)\Microsoft SDKs\MPI\Include /link /LIBPATH:C:\Program Files (x86)\Microsoft SDKs\MPI\Lib\x64 msmpi.lib逐段解释这个命令cl调用MSVC编译器。/EHsc启用C异常处理MS-MPI的头文件里有throw()之类的异常规格不加上这个参数有时会报编译警告甚至错误。/I指定额外的头文件搜索路径告诉编译器去MS-MPI的Include文件夹找mpi.h。/link后面跟的内容传给链接器。/LIBPATH指定导入库的搜索路径这里用的是64位版本的库。如果你在32位环境则改用Lib\x86。msmpi.libMS-MPI的导入库文件名链接器需要它来解析MPI_Init等函数的入口地址。编译成功后当前目录下会生成test_mpi.exe。没有报错信息就是最好的结果。如果中间报错别急着改代码八成是路径或者参数问题后面章节我专门列了排查表。3.3 用mpiexec启动多进程运行编译出exe后用mpiexec启动多个进程mpiexec -n 4 test_mpi.exe这里的-n 4表示启动4个MPI进程。实际运行时这4个进程可能被分配到机器的不同核心上由MS-MPI的进程管理器统一调度。执行后你会看到类似下面的输出进程 1/4 计算了 [250, 500) 的和局部结果 250 进程 3/4 计算了 [750, 1000) 的和局部结果 250 进程 0/4 计算了 [0, 250) 的和局部结果 250 进程 2/4 计算了 [500, 750) 的和局部结果 250 全局求和结果 1000预期结果 1000注意输出顺序可能是乱的这是正常的因为4个进程是并行执行的谁先跑完谁先打印。核心看两点第一4个进程都成功启动了没有报错第二最后的全局求和结果等于1000说明通信和归约逻辑正确。我实测的时候还试过-n 8、-n 16程序会自动把数组切成对应份数效果都正常。这说明程序的写法考虑了进程数任意的情况不会因为进程数变化而出错这也是写并行程序时要养成的意识。3.4 用Visual Studio配置项目图形化调试并行程序如果你需要在程序里打断点调试命令行编译显然不够方便。在VS里新建一个C空项目把test_mpi.c添加进去然后做几步配置右键项目 - “属性”在“VC目录”里“包含目录”添加C:\Program Files (x86)\Microsoft SDKs\MPI\Include“库目录”添加C:\Program Files (x86)\Microsoft SDKs\MPI\Lib\x64接着在“链接器”-“输入”-“附加依赖项”里添加msmpi.lib。配置好之后按F7编译。如果要调试并行程序不能直接F5需要在“调试”-“命令参数”里加上mpiexec的参数调用方式。更方便的办法是先编译通过然后用VS的“配置属性”-“调试”中的“命令”填C:\Program Files\Microsoft MPI\Bin\mpiexec.exe“命令参数”填-n 2 $(TargetPath)这样按F5时VS会自动调用mpiexec启动两个进程每个进程都能在VS里打断点观察变量并行调试的体验比命令行不知道舒服多少倍。不过要注意多进程调试时VS会同时打开多个调试会话每个进程的断点都会命中需要小心区分当前调试的是哪个rank。4. 常见问题与排查实录4.1 高频报错速查表我把自己和身边同事踩过的坑整理成了一张表按报错信息分类方便你对号入座错误信息原因分析解决方案mpiexec 不是内部或外部命令运行时未安装或未加入PATH安装msmpisetup.exe检查PATH是否包含MPI\Bin目录无法打开包括文件: mpi.h编译时未指定头文件路径编译命令加/I参数指向SDK的Include目录无法解析的外部符号 MPI_Init链接时未正确引用msmpi.lib检查是否在link处加了库路径和库文件名error LNK2038: 运行时库不匹配项目使用MDd但库是MD或相反VS项目里统一“代码生成”-“运行库”为“多线程(/MT)”或“多线程DLL(/MD)”无法启动此程序因为计算机中丢失 MSMPI.dll目标机器只装了SDK没装运行时安装msmpisetup.exe运行时和SDK是两回事多机运行连接失败防火墙阻止、凭据配置不对允许MPI进程通信的入站规则检查MPI证书或密码设置4.2 常见问题的详细排查过程问题1进程启动一两个就失败或者卡住不动。这个多半不是代码问题而是mpiexec的进程启动方式造成的。MS-MPI默认在同机多进程运行时采用smpd的方式管理。如果你发现进程数一多就崩溃试试加参数mpiexec -n 4 -signed test_mpi.exesigned参数会要求所有进程签名一致有时候能绕过一些权限相关的坑。如果是跨机器运行需要确保所有机器上的账号有相同密码或者配置好MPI证书否则连不上。问题2编译报错“宏定义冲突”或者头文件重复定义。MS-MPI的头文件在某些版本下跟Windows.h同时包含时会产生宏冲突比如min、max宏。解决办法是在包含windows.h之前先定义WIN32_LEAN_AND_MEAN或者把MPI头文件的包含放在最后#define WIN32_LEAN_AND_MEAN #include windows.h #include mpi.h问题364位和32位混淆。这是个非常隐蔽的坑。如果你用x64命令行去链接Lib\x86下的库链接器会报“模块计算机类型与目标计算机类型冲突”。反过来也一样。务必保证编译器的架构和库路径一致x64编译器配Lib\x64x86编译器配Lib\x86。问题4命令行能编译但VS里编译报错。这通常是因为VS平台选成了Win32而你的库路径里用的是x64。去项目属性“配置管理器”把“活动解决方案平台”改成x64同时配置“VC目录”里的库目录为Lib\x64。问题5程序在单机上跑得很稳放到别的Windows机器上就报错。优先检查那台机器装没装MS-MPI运行时。SDK只是开发用的不包含运行时这个我前面强调过但真到部署时还是会忘。4.3 针对多机并行环境的配置经验单机跑通只是第一步实际工作中更常见的是多节点并行。MS-MPI的多机运行要求每个节点都有相同版本的运行时并且用户名密码一致。配置好之后用类似这样的命令指定主机列表mpiexec -hosts 2 node1,node2 -n 8 test_mpi.exe意思是两个节点上一共启动8个进程。第一次跑之前建议先用mpiexec -validate node1 node2验证节点间的连通性与配置是否正确这个命令会检查远程节点的smpd服务是否在运行、账号是否有权限省去很多瞎猜的功夫。我在公司里实际部署时发现防火墙经常把MPI的动态端口通信拦住。最简单的办法是在防火墙上放行msmpiexec.exe和smpd.exe两个程序或者直接放行C:\Program Files\Microsoft MPI\Bin\目录。如果公司安全策略很严格那就需要联系网管申请固定端口范围MS-MPI的端口范围可以通过环境变量MSMPI_DPORT控制具体值可以查官方文档。5. 备选路线用WSL在Windows里跑Linux版MPI5.1 什么时候我应该考虑WSL方案不是所有人都适合直接上MS-MPI。如果你是在校学生课堂作业和教材例子全是在Linux下写的用的命令是mpicc和mpirun那直接在Windows里装个WSL体验更接近原本的学习环境。WSLWindows Subsystem for Linux能让你在Windows里直接运行一个完整的Linux用户态环境。对于MPI这种依赖系统调用的计算框架来说WSL的性能损失很小因为微软对计算密集型场景做了大量优化。CPU密集型的MPI程序在WSL里的运行速度跟原生Linux差距很小可以忽略。5.2 WSL下快速搭建MPI环境开启WSL的步骤以管理员身份打开PowerShell执行wsl --install装完重启系统会引导你设置Linux用户名和密码。默认装的是Ubuntu这就够用了。进入Ubuntu终端后依次执行sudo apt update sudo apt install -y mpich如果你的需求跟OpenMPI更契合也可以装sudo apt install -y openmpi-bin libopenmpi-dev装完验证一下mpicc --version which mpirun然后编译运行同一个测试程序mpicc -o test_mpi test_mpi.c mpirun -n 4 ./test_mpi因为Linux下的MPI生态很成熟mpicc这个编译器封装脚本会自动处理头文件路径和库路径不需要手动配置任何东西所以体验确实比Windows原生路顺畅不少。5.3 WSL方案的一些注意点WSL方案也不是没有缺点。首先跨文件系统的读写性能比较差如果你的源码和数据放在Windows盘的/mnt/c/目录下编译和运行速度会明显变慢建议把项目放在WSL自己的Linux文件系统里也就是家目录~下。其次WSL里跑MPI程序如果想跟Windows原生程序交互比如调用Windows下编译好的动态库会有很大的限制这在当前场景下倒是影响不大。还有一点WSL默认的内存分配是宿主机内存的50%左右如果你的并行程序很吃内存需要去创建C:\Users\你的用户名\.wslconfig文件里面设定memory8GB之类的参数然后重启WSL才生效。这些细节官方文档都有实际用到时再查也不迟。6. 性能验证与开发效率确认你的环境真的没问题6.1 用系统性的方法验证MPI环境是否正常环境搭好了、示例程序也跑通了但对开发来说这只是开始。我强烈建议跑一遍性能基线测试确认你的MPI环境不只能跑通而且通信性能是合理的。MS-MPI的SDK里自带了一个叫MPI-PingPong之类的测试程序新版SDK可能叫pingpong.exe或mpi_pingpong在SDK安装目录的Examples文件夹下能找到。跑这个程序观察固定大小消息的往返延迟。我自己跑的参考数值大概是同机双进程、64字节消息、MS-MPI v10延迟在几微秒到十几微秒之间不同CPU和Windows调度策略下会有波动。如果延迟远超这个量级比如几十上百微秒那就要检查是不是杀毒软件在扫描通信进程、或者系统电源策略把CPU频率压得太低。6.2 后续扩展方向分布式计算框架与MPI的对接MPI环境搭好之后能做的事情远不止跑几个demo程序。常见的扩展方向有两个一是对接分布式计算框架。虽然现在很多大数据组件基于RPC或者混合通信模式但科学计算、仿真求解器、深度学习训练里的AllReduce通信原语底层算法依然大量参考或者直接使用MPI的实现思路。理解了MPI的进程模型和通信模型之后再去看Horovod这类分布式训练框架的源码会清晰很多。二是实践中提升并行程序的可扩展性。从4进程跑到16进程你会发现效率并不总是线性提升经常有通信开销、负载不均衡的问题冒出来。这时候你可以用MPI的MPI_Wtime接口精确统计每个进程的计算耗时和通信耗时定位瓶颈在哪一段。把基础环境问题解决掉后面才能把精力花在真正的并行算法优化上。7. 个人经验总结Windows下搭MPI环境的几个建议环境搭建看起来是个一次性工作但实际用起来总会有各种边界情况冒出来。我做事情有个习惯每次搭完环境、能在目标机器上稳定运行之后会把Windows下的环境配置步骤自己记一份文档包括PATH配了哪些路径、SDK版本和运行时版本、有没有特殊的编译参数。因为半年之后你可能会在新机器上重新配一遍到时候发现官网改版下载路径变了或者SDK版本不一致导致头文件接口变了那份文档比任何博客都有用。至于先学MS-MPI还是直接用WSL我的建议是如果你以后的工作或科研大概率接触Linux服务器直接学WSL方案命令通用性更强如果你确定就在Windows环境里打转MS-MPI VS这条路更稳。两条路都会的人实际工作中会很吃香毕竟不是所有时候你都能选运行环境。我在实际配置中还有一个心得不要在一开始就追新版本。MS-MPI 10.x很稳定OpenMPI的版本也够用但“最新版”有时候意味着生态还没跟上遇到奇怪的问题时社区问答和文档都可能滞后。选一个已经被大量验证的版本把精力花在应用开发上这才是更实际的决策。希望这篇文章能帮你少走点弯路把Windows上的MPI环境顺顺利利跑起来。
返回列表