LoginSignup
32
22

More than 5 years have passed since last update.

MacにMPI+OpenMPハイブリッド並列環境を構築する

Last updated at Posted at 2017-01-13

はじめに

Macに入っているgcc/g++はclang/clang++であり、Macのclangは現時点(2017年1月13日)ではOpenMPに対応していない。

したがってMacでOpenMPに対応したC/C++コンパイラを手に入れるには、

  • LLVM公式サイトからOpenMPに対応したclangを持ってくる
  • GNUのgcc/g++を使う

の2つの方法がある。後者の方が楽だが、そうすると例えばbrewでopenmpiをインストールした場合、mpiccやmpic++がclangやclang++を見に行ってしまう。これをなんとかするためには、

  • OpenMPIの公式サイトからソースを持ってきて、コンパイラとしてGNUのgcc/g++を使うように設定してビルドする
  • mpicc/mpic++を使うのをあきらめる

の2つの方法がある。

ポリシーに個人差はあると思われるが、僕の経験では

  • コンパイラとしてGNUのgcc/g++を使う
  • MPIライブラリとしてOpenMPIを入れるが、mpicc/mpic++は使わない

というソリューションが一番楽だったので、その方法をまとめる。

なお、環境はmacOS Sierra 10.12.3。

インストールと設定手順。

gccとopenmpiをbrewで入れる。まずgcc。

$ brew install gcc --without-multilib

記事執筆時点ではgccとして6.3.0が入る。なお、デフォルトではmulti-libオプションが付加されてしまう。multi-libというのは他のアーキテクチャのためのバイナリを作成する機能で、これを設定すると OpenMPの動作に問題があるらしい。実際、
--without-multlibをつけないでインストールすると、こんなことを言われる。

GCC has been built with multilib support. Notably, OpenMP may not work:
  https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60670
If you need OpenMP support you may want to
  brew reinstall gcc --without-multilib

このオプションをつけなかった場合でもOpenMPは動いたりするようなのだが、妙な不具合があると嫌なので、--without-multilibオプションをつけておくと良い。

/usr/local/binにgcc-6やg++-6という名前でシンボリックリンクが貼られるので、さらにgcc、g++という別名を与える。

$ cd /usr/local/bin
$ ln -s gcc-6 gcc
$ ln -s g++-6 g++

さらに、/usr/local/binを先に見るようにパスを通す。

$ export PATH=/usr/local/bin:$PATH

動作確認したら.zshrcとかに入れておこう。

次はopenmpi。

$ brew install openmpi --with-cxx-bindings

MPIのC++ bindingsはMPI3.0からdeprecatedなのだが、まだ使ってるコードがあったので指定した。ここまでで設定は完了。

コンパイル

現時点では、mpiccやmpic++がclang/clang++を見に行ってしまう。

$ mpicc -v
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin16.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

で、これをなんとかする方法もないわけではないが、mpiccやmpic++はどうせインクルードファイルとライブラリの管理しかしてないので、そこを手で書いてしまう。

例えば以下のようなMPI/OpenMPハイブリッド並列コードを考える。

hybrid.cpp
#include <stdio.h>
#include <omp.h>
#include <mpi.h>
int
main(int argc, char** argv){
  int rank, size;
  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD,&rank);
  MPI_Comm_size(MPI_COMM_WORLD,&size);
  const int num_threads = omp_get_max_threads();
#pragma omp parallel for
  for(int i=0;i<num_threads;i++){
    printf("MPI rank = %d/%d;thread d = %d/%d\n",rank,size,omp_get_thread_num(),num_threads);
  }
  MPI_Finalize();
}

これは、以下のようにしてコンパイル、リンクできる。

$ g++ -fopenmp -I/usr/local/opt/open-mpi/include -L/usr/local/opt/open-mpi/lib -lmpi hybrid.cc

いちいち-Iとか-Lとか指定するのは面倒だと思う人がいるかもしれないが、どうせmakefileかなにか使ってるでしょ?そこに、

CPPFLAGS=-fopenmp -I/usr/local/opt/open-mpi/include
LDFLAGS= -L/usr/local/opt/open-mpi/lib -lmpi

って書けばいいことなので、特に不便は感じない。1

もし、コンパイラが常にMPI関連のインクルードパスやライブラリパスを見に行ってもかまわない、ということであれば、.zshrcか何かに以下の記述をすれば良い。

.zshrc
export MPIPATH=/usr/local/opt/open-mpi
export C_INCLUDE_PATH=$MPIPATH/include:$C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=$MPIPATH/include:$CPLUS_INCLUDE_PATH
export LIBRARY_PATH=$MPIPATH/lib:$LIBRARY_PATH

これでいちいちmakefileにパスを設定しなくても、g++ test.cpp -lmpiとかだけでMPIプログラムをコンパイルできるようになる。

さて、実行して、たしかにMPI/OpenMPハイブリッド並列ができているか確認する。

$ OMP_NUM_THREADS=3 mpirun -np 2 ./a.out  # 3スレッド*2プロセス
MPI rank = 0/2;thread d = 0/3
MPI rank = 0/2;thread d = 1/3
MPI rank = 0/2;thread d = 2/3
MPI rank = 1/2;thread d = 0/3
MPI rank = 1/2;thread d = 2/3
MPI rank = 1/2;thread d = 1/3

$ OMP_NUM_THREADS=2 mpirun -np 3 ./a.out # 2スレッド*3プロセス
MPI rank = 0/3;thread d = 0/2
MPI rank = 0/3;thread d = 1/2
MPI rank = 1/3;thread d = 0/2
MPI rank = 1/3;thread d = 1/2
MPI rank = 2/3;thread d = 0/2
MPI rank = 2/3;thread d = 1/2

まとめ

結局インストール+設定でやったことは

$ brew install gcc
$ brew install openmpi
$ cd /usr/local/bin
$ ln -s gcc-6 gcc
$ ln -s g++-6 g++
$ export PATH=/usr/local/bin:$PATH

だけなので楽ちん。その代わりmpicc/mpic++が使えないけど、makefileに

CPPFLAGS=-fopenmp -I/usr/local/opt/open-mpi/include
LDFLAGS= -L/usr/local/opt/open-mpi/lib -lmpi

って書くか、.zshrc

.zshrc
export MPIPATH=/usr/local/opt/open-mpi
export C_INCLUDE_PATH=$MPIPATH/include:$C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=$MPIPATH/include:$CPLUS_INCLUDE_PATH
export LIBRARY_PATH=$MPIPATH/lib:$LIBRARY_PATH

と書くだけなので、これが一番楽なんじゃないかなぁ。

追記(2017/8/17)

この記事の方法だと、brewをアップデートしてGCCがVer. 6からVer. 7になった時に gccやg++のシンボリックリンクが切れて、clang/clang++の方を見に行ってしまいますね。

もう一度

$ cd /usr/local/bin
$ rm gcc
$ ln -s gcc-7 gcc
$ rm g++
$ ln -s g++-7 g++

ってやり直せばいいんだけど、やっぱりあまりよくないかなぁ・・・


  1. 僕は、こういう環境依存するところは例えばmakefile.optとかに書いて、makefile本体からは-include makefile.optとしてインクルードするようにしておいて、makefileはリポジトリに入れ、makefile.optはignore指定している。makefile内で環境判定するより楽な気がする。 

32
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
22