如何使用 automake 编译同一程序的 MPI 和非 MPI 版本?

Posted

技术标签:

【中文标题】如何使用 automake 编译同一程序的 MPI 和非 MPI 版本?【英文标题】:how to compile MPI and non-MPI version of the same program with automake? 【发布时间】:2010-10-19 13:10:01 【问题描述】:

我有一个可以使用 MPI 支持编译的 C++ 代码,具体取决于 某些预处理器标志;缺少适当的标志,来源 编译成非并行版本。

我想设置 Makefile.am 以便它编译 both MPI 并行顺序版本,如果可以选择 ./configure 已给出。

这里有一个问题:MPI 有自己的 C++ 编译器包装器,并且坚持 使用它而不是标准来编译和链接源 C++ 编译器。如果我要自己编写 Makefile,我将不得不 做这样的事情:

myprog.seq: myprog.cxx
    $(CXX) ... myprog.cxx

myprog.mpi: myprog.cxx
    $(MPICXX) -DWITH_MPI ... myprog.cxx

有没有办法告诉 automake 它必须改用 $(MPICXX) 编译启用 MPI 的程序版本时的 $(CXX)?

【问题讨论】:

您是否关心串行程序是否与“同时构建”情况下的 MPI 库链接?如果没有,您可以同时使用 mpicxx —— 它不会伤害任何东西。如果您不构建 mpi 版本,则可以为所有内容设置使用 g++。根据经验,这是许多同时具有并行和串行版本的软件包似乎都在做的事情——我想到了 hdf5。 一般来说不会,我以前也是这样编译的。不过,我开发了一些 PMPI 工具,通常您不想将拦截器库链接到 MPI,如果您使用 mpicc 编译,您将不得不这样做。 【参考方案1】:

我有同样的问题,我发现没有真正好的方法让自动工具有条件地为特定目标使用 MPI 编译器。 Autotools 擅长根据您的源代码编写的语言(CCCXXFCF77 等)确定要使用的编译器,但它确实不擅长弄清楚是否对特定目标使用 MPI 编译器。您可以设置 MPICC、MPICXX 等,但如果您以这种方式使用编译器,则基本上必须为您的目标重写所有 Makefile 规则(如上所述)。如果你这样做了,写一个 automake 文件有什么意义呢?

其他人建议像使用外部库一样使用 MPI,这是我提倡的方法,但您不应该手动操作,因为不同的 MPI 安装具有传递给编译器的不同标志集,并且它们可能取决于您正在编译的语言。

好消息是我所知道的所有当前发布的 MPI 编译器都支持自省参数,例如 -show-show-compile-show-link。您可以自动从脚本中提取参数。

所以,我处理这个问题的方法是制作一个m4 脚本,该脚本从 MPI 编译器中提取定义、包含、库路径、库和链接器标志,然后将它们分配给您可以在您的Makefile.am。这是脚本:

lx_find_mpi.m4

这使得 MPI 以 automake 期望的方式工作。顺便说一句,这是 CMake 在他们的 FindMPI 模块中使用的方法,我发现它在那里工作得很好。它使构建更加方便,因为您可以为您的目标做这样的事情:

bin_PROGRAMS = mpi_exe seq_exe

# This is all you need for a sequential program
seq_exe_SOURCES = seq_exe.C

# For an MPI program you need special LDFLAGS and INCLUDES
mpi_exe_SOURCES = mpi_exe.C
mpi_exe_LDFLAGS = $(MPI_CXXLDFLAGS)

INCLUDES = $(MPI_CXXFLAGS)

其他语言也有类似的标志,因为就像我说的那样,特定的标志和库可能会根据您使用的语言的 MPI 编译器而有所不同。

lx_find_mpi.m4 还设置了一些 shell 变量,以便您可以在 configure.ac 文件中测试是否找到了 MPI。例如,如果您正在寻找 MPI C++ 支持,您可以测试 $have_CXX_mpi 以查看宏是否找到它。

我已经使用mvapich 和OpenMPI 测试了这个宏,以及BlueGene 机器上的自定义MPICH2 实现(尽管它没有解决您将在那里看到的所有交叉编译问题) .让我知道如果有什么不起作用。我想保持宏尽可能健壮。

【讨论】:

你应该将它添加到 LDADD 而不是 LDFLAGS 否则一些链接器会抛出错误。并且脚本实际有一个错误:使用 sed,要删除尾随空格,您应该使用 sed/ +/ /g 而不是 sed/ */ /g。 你的宏对我来说运行良好。但是,我必须运行两次才能从 MPICC 和 MPICXX 中获取值。在第二次调用中,我将宏包装在AC_LANG_PUSH([C++])AC_LANG_POP([C++]) 之间。感谢 SW。【参考方案2】:

很抱歉,让 automake 使用 MPI 非常困难。我已经为此苦苦挣扎了好几个月,试图找到一个好的解决方案。我有一个源代码树,它有一个库,然后在使用该库的子文件夹中有许多程序。一些文件夹是 mpi 程序,但是当我尝试用 Makefile.am 中的 MPI 编译器替换 CXX 时。

if USE_MPI
  MPIDIR = $(MPICOMPILE)
  MPILIB = $(MPILINK)
  CXX=@MPICXX@
  F77=@MPIF77@
  MPILIBS=$(MPILINK)
endif

我明白了

CXX was already defined in condition TRUE, which includes condition USE_MPI ...
configure.ac:12: ... `CXX' previously defined here

我没有指定编译器的规则,所以也许有办法做到这一点。

SUBDIRS = .
bin_PROGRAMS = check.cmr
check_ccmr_SOURCES = check_gen.cpp
check_ccmr_CXXFLAGS = -I$(INCLUDEDIR) $(MPIDIR)
check_ccmr_LDADD = -L$(LIBDIR)
check_ccmr_LDFLAGS = $(MPILIB)

【讨论】:

【参考方案3】:

如果您已将subdir-objects 选项禁用为automake,则类似的操作可能会起作用:

配置.ac:

AC_ARG_ENABLE([seq], ...)
AC_ARG_ENABLE([mpi], ...)
AM_CONDITIONAL([ENABLE_SEQ], [test $enable_seq = yes])
AM_CONDITIONAL([ENABLE_MPI], [test $enable_mpi = yes])
AC_CONFIG_FILES([Makefile seq/Makefile mpi/Makefile])

Makefile.am:

SUBDIRS =
if ENABLE_SEQ
SUBDIRS += seq
endif
if ENABLE_MPI
SUBDIRS += mpi
endif

来源.am:

ALL_SOURCES = src/foo.c src/bar.cc src/baz.cpp

seq/Makefile.am:

include $(top_srcdir)/sources.am

bin_PROGRAMS = seq
seq_SOURCES = $(ALL_SOURCES)

mpi/Makefile.am:

include $(top_srcdir)/sources.am

CXX = $(MPICXX)
AM_CPPFLAGS = -DWITH_MPI

bin_PROGRAMS = mpi
mpi_SOURCES = $(ALL_SOURCES)

阻止您在同一目录中执行这两项操作的唯一方法是覆盖 $(CXX)。例如,您可以设置 mpi_CPPFLAGSautomake 会很好地处理这个问题,但是编译器开关在这里不行。

【讨论】:

无评论投反对票。做得好。我承认这有点肮脏,但你有更好的主意吗?【参考方案4】:

不使用不同来源的可能解决方法是:

myprog.seq: myprog.cxx
    $(CXX) ... myprog.cxx

myprog-mpi.cxx: myprog.cxx
    @cp myprog.cxx myprog-mpi.cxx

myprog.mpi: myprog-mpi.cxx
    $(MPICXX) -DWITH_MPI ... myprog-mpi.cxx
    @rm -f myprog-mpi.cxx

对于 Automake:

myprog-bin_PROGRAMS = myprog-seq myprog-mpi

myprog_seq_SOURCES = myprog.c

myprog-mpi.c: myprog.c
    @cp myprog.c myprog-mpi.c

myprog_mpi_SOURCES = myprog-mpi.c
myprog_mpi_LDFLAGS = $(MPI_CXXLDFLAGS)

INCLUDES = $(MPI_CXXFLAGS)
BUILT_SOURCES = myprog-mpi.c
CLEANFILES = myprog-mpi.c

【讨论】:

聪明的解决方法,谢谢!我认为有一个错字:第二行 myprog..._SOURCES 应该是 myprog_mpi_SOURCES = myprog-mpi.c 然后是 mpyorog_mpi_LDFLAGS = ... 您还必须将 myprog-mpi.c 列为使用 BUILT_SOURCES = myprog-mpi.c 自动生成的。【参考方案5】:

这是我为构建两个静态库而提出的解决方案 - 一个带有 MPI (libmylib_mpi.a),一个没有 (libmylib.a)。这种方法的优点是不需要重复的源文件、两个变体的单个 Makefile.am 以及使用子目录的能力。您应该能够根据需要修改它以生成二进制文件而不是库。我像往常一样构建非 MPI 库,然后对于 MPI 变体,我将_SOURCES 留空并改用_LIBADD,为目标文件指定.mpi.o 的扩展名。然后我指定一个规则来使用 MPI 编译器生成 MPI 目标文件。

整体文件/目录结构类似于

configure.ac
Makefile.am
src
    mylib1.cpp
    mylib2.cpp
    ...
include
    mylib.h
    ...

配置.ac:

AC_INIT()
AC_PROG_RANLIB
AC_LANG(C++)
AC_PROG_CXX
# test for MPI, define MPICXX, etc. variables, and define HAVE_MPI as a condition that will evaluate to true if MPI is available and false otherwise.
AX_MPI([AM_CONDITIONAL([HAVE_MPI], [test "1" = "1"])],[AM_CONDITIONAL([HAVE_MPI], [test "1" = "2"])]) #MPI optional for xio
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

可能有比我在此处列出的更有效的条件检查方法(欢迎提出建议)。

Makefile.am:

AUTOMAKE_OPTIONS = subdir-objects
lib_LIBRARIES = libmylib.a
libmylib_a_SOURCES = src/mylib_1.cpp src/mylib_2.cpp ...

#conditionally generate libmylib_mpi.a if MPI is available
if HAVE_MPI
    lib_LIBRARIES += libmylib_mpi.a
    libmylib_mpi_a_SOURCES = #no sources listed here
    #use LIBADD to specify objects to add - use the basic filename with a .mpi.o extension
    libmylib_mpi_a_LIBADD = src/mylib_1.mpi.o src/mylib_2.mpi.o ...
endif
AM_CPPFLAGS = -I$srcdir/include

include_HEADERS = include/mylib.h

# define a rule to compile the .mpi.o objects from the .cpp files with the same name
src/%.mpi.o: $srcdir/src/%.cpp $srcdir/include/mylib.h
    $(MPICXX)  $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -DWITH_MPI=1 -c $(patsubst %.mpi.o,$(srcdir)/%.cpp,$@) -o $@

#define a rule to clean the .mpi.o files
clean-local:
    -rm -f src/*.mpi.o

【讨论】:

【参考方案6】:

MPI 安装确实(通常)附带编译器包装器,但不要求您使用它们——MPI 确实坚持它。如果您想走自己的路,您可以编写自己的 makefile 以确保 C++ 编译器获得正确的库(等)。要找出正确的库(等)是什么,请检查编译器包装器,在我使用的所有系统上,它是一个 shell 脚本。

乍一看,英特尔编译器等产品附带的编译器包装器有点令人生畏,但停下来想想发生了什么——您只是在编译一个使用一两个外部库的程序。编写一个使用 MPI 库的 makefile 并不比编写一个使用任何其他库的 makefile 困难。

【讨论】:

感谢您的回答。尽管我不需要使用 MPI 编译器包装器,但是那里有许多 MPI 实现,并且每个都使用自己的库名称等——我宁愿重写 Makefile 节以使用 $(MPICXX) 而不是维护几行 automake/autoconf 代码为每个 MPI 版本提供 CPPFLAGS/LDFLAGS/LIBS... 如果您只关心一个平台,那就不再困难了。但是,我在 3 或 4 个不同的集群架构上运行,虽然它们都共享一个 mpicc 编译器,但不同平台的特定标志和库是不同的。除非您在脚本,这是我建议做的。看我的回答。

以上是关于如何使用 automake 编译同一程序的 MPI 和非 MPI 版本?的主要内容,如果未能解决你的问题,请参考以下文章

水文日常~~Linux命令行下如何编译运行MPI程序(C/C++)

水文日常~~Linux命令行下如何编译运行MPI程序(C/C++)

水文日常~~Linux命令行下如何编译运行MPI程序(C/C++)

水文日常~~Linux命令行下如何编译运行MPI程序(C/C++)

错误:libtool - 编译 MPI 程序时

交叉编译一个automake项目?