9.7. 构建完全静态的MPI应用程序

9.7.1. 你真的需要完全静态吗?

没有人会意外进入这个文档部分。

如果您正在阅读这段文字,很可能是因为您希望解决某个问题,而完全静态的MPI应用程序似乎是正确的解决方案。人们通常认为完全静态的MPI应用程序可以解决以下两个常见问题:

  1. MPI应用程序无法启动,因为运行时找不到依赖的共享库。

  2. 大规模启动时的文件系统性能极差。

如果这些问题中的任何一个困扰着你,Open MPI社区强烈建议你使用其他机制来解决问题:完全静态的MPI应用程序虽然可行,但由于其他原因并非最优选择。

以下部分将讨论上述问题。

9.7.1.1. 运行时查找依赖的共享库

如果在运行时遇到找不到共享库的问题——尤其是在与调用mpirun的节点不同的远程节点上——最佳解决方案是在所有节点(包括远程节点)上正确设置LD_LIBRARY_PATH(或等效机制)。

这是一个复杂的话题,但即便如此,解决这个问题通常比创建和维护静态构建更简单。详情请参阅运行MPI应用程序部分

9.7.1.2. 大规模提升文件系统性能

在v5.0.0版本之前,Open MPI将大量插件编译为独立的动态共享对象(DSOs)——即文件系统中的独立文件。这些DSOs中的许多会在运行时被每个MPI进程加载。

这可能导致文件系统拥塞,尤其是当Open MPI安装在网络文件系统上并启动大型作业时:许多节点将同时与文件服务器通信,并可能需要传输大量小型文件。

从v5.0.0版本开始,默认情况下,Open MPI的插件不再构建为动态共享对象(DSO)。因此,Open MPI在启动时通常只加载少量共享库。即使Open MPI安装在网络文件系统上,随着时间的推移这些库很可能会被节点缓存,因此在启动MPI作业时产生的网络文件系统流量相对较小。

简而言之:与之前版本相比,Open MPI v5.0.x对网络文件系统的影响已大幅降低。现在不再需要编译完全静态的应用程序来消除启动时打开每个DSO文件的行为。

9.7.1.3. 完全静态应用的其他弊端

以下是完全静态MPI应用程序不够理想的几个其他原因:

  1. 当应用程序静态链接所有依赖项时,操作系统无法在多个进程副本之间共享代码。

    例如,如果在节点上启动N个完全静态链接的MPI应用程序副本,将消耗(N * 应用程序大小)字节的RAM。相反,启动N个动态链接的MPI应用程序副本——其中每个副本具有相同的依赖库——每个共享依赖库只会被加载到RAM中一次。

    换句话说:使用动态链接可以节省内存。

  2. 完全静态的应用程序不会链接到动态链接库,例如Linux上的libdl,该库提供了dlopen(3)dlsym(3)等功能。这将破坏依赖此类接口的Open MPI功能。

    警告

    Open MPI的内存管理功能(在InfiniBand等绕过操作系统的网络上提供重要性能优化)需要dlsym(3)接口,因此无法在完全静态的应用程序中工作。

现在你被说服了吗?如果可能的话,请尽量避免构建完全静态的MPI应用程序。

9.7.2. 构建完全静态的MPI应用程序

警告

如果在阅读完上述所有内容后,您仍然坚持要构建完全静态的MPI应用程序,请注意完全静态链接并不适合胆小者,也不推荐使用。但在某些注意事项下,这是可能实现的。

  1. 您的程序链接的所有组件都必须提供静态库。这包括Open MPI;您必须使用Open MPI的configure命令中的--enable-static选项,或者确保Open MPI库的静态版本可用。

    注意

    某些Linux发行版默认可能不包含常用Linux库的静态版本(例如libnuma),或者需要安装额外的RPM包才能获得对应的静态库。

  2. Open MPI必须在不包含内存管理器的情况下构建。这意味着Open MPI必须使用--without-memory-manager标志进行配置。在某些Open MPI没有内存管理器的平台上这无关紧要,但在某些平台上这是必需的(如Linux系统使用许多操作系统旁路网络时)。在Open MPI没有内存管理器的平台上使用此标志是无害的。

    重要提示

    不包含内存管理器支持可能会导致Open MPI与操作系统旁路网络一起使用时性能降低。

以下是在Linux上配置Open MPI以构建完全静态库的方法:

shell$ ./configure --without-memory-manager --disable-dlopen \
    --enable-static --disable-shared ...

--disable-shared 标志是可选的;它将阻止 Open MPI 同时构建共享库。

或者,您也可以尽可能多地使用静态库构建Open MPI,但通过省略--disable-dlopen标志来保留dlopen功能:

shell$ ./configure --without-memory-manager \
    --enable-static --disable-shared ...

这将为您提供一个基本静态构建的Open MPI,但保留了至少部分动态库的优势。

9.7.2.1. 包含完整归档文件

某些系统可能对其支持库有额外的约束条件,需要采取额外步骤才能生成可正常工作的完全静态MPI应用程序。例如,任何拥有自身运行时插件系统(即在运行时加载动态共享对象("DSOs"))的库,在生成完全静态构建时都会面临额外的复杂性。

在这种情况下,通常需要运行mpicc ... --showme来查看 Open MPI封装命令将使用的编译器/链接器命令,然后为运行时所需的 DSO插件静态版本添加链接器参数来增强这些命令。

例如,如果您有一个libfoo.a在运行时动态加载plugin.so,您将需要一个plugin.a,并且——假设使用GNU链接器——添加类似以下的参数:

  • -static: 告知链接器生成静态可执行文件。

  • -Wl,--whole-archive -lfoo /path/to/plugin.a -Wl,--no-whole-archive: 告诉链接器将整个foo库和整个plugin.a归档文件包含在可执行文件中。

您可以选择手动在命令行中添加这些参数,或者修改包装器编译器的默认行为以向最终用户隐藏这种复杂性(但请注意,如果修改了包装器编译器的默认行为,所有用户都将创建静态应用程序!)。