はじめに
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ハイブリッド並列コードを考える。
#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
か何かに以下の記述をすれば良い。
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
に
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++
ってやり直せばいいんだけど、やっぱりあまりよくないかなぁ・・・
-
僕は、こういう環境依存するところは例えばmakefile.optとかに書いて、makefile本体からは
-include makefile.opt
としてインクルードするようにしておいて、makefileはリポジトリに入れ、makefile.optはignore指定している。makefile内で環境判定するより楽な気がする。 ↩