16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

macOSでC, C++並列処理をするためのOpenMP, OpenMPI環境構築

Last updated at Posted at 2021-02-26

2021/02/27投稿

※最新版はharoot.netに移動しました。

はじめに

初投稿です。
自分がmacにOpenMPとOpenMPIを導入する際かなり混乱したため、自分のような初心者でもわかるように書きました。参考になれば幸いです。

並列処理

プログラムは基本1つの実行に対して1つのコアかつ1つのスレッドしか使用しません。

そのためPC内の他のコアは常に暇しており、また計算以外の処理中などは使用しているコア自体も手持ち無沙汰になっています。
簡単なプログラムであれば問題ありませんが、プログラムによっては計算時間が非常にかかるため、暇している他のコアに仕事を振ったり、使用しているコアにもスレッドを分けて複数の仕事を与えたいところです。

そこで並列処理を行います。プログラム内のデータを各スレッドや各コアに分散させ、計算が終わったところで各スレッドや各コアからデータを収集します。

今回導入するOpenMP、OpenMPIはそれぞれスレッド、コアを並列化することが出来ます。

OpenMPとOpenMPI

OpenMPはスレッド並列化を行うAPIです。 1つのCPUが1つのコアしか持っていなくても、複数の処理を同時に行うことができます。
OpenMPIはMPI(Message Passing Interface)の一種で、プロセス自体を並列化するライブラリでありOpenMPとは全くの別物です。1つの命令を1つのコアではなく複数のコア(プロセス)を用いて処理します。

これらを同時に使用することで、各コアがそれぞれ複数の処理を同時に行うことができるようになり、計算速度が上がります。

これらはfortran, C言語, C++で使用する事が出来ますが、今回はC言語, C++での設定を行います。またOpenMPIは複数ノードを用いた設定もありますが、今回はしていません。

環境 

macOS Sierra 10.12.6
macOS Mojave 10.14.6
macOS Catalina 10.15.7

gcc(OpenMP)の導入

OpenMPはmacの標準のCコンパイラ(clang)ではサポートされていません。
なので今回は、OpenMPがサポートされているgccにコンパイラを切り替える事で、OpenMPの導入をしようと思います。

※自分のアカウントが一般ユーザーの場合、スーパーユーザー(管理者権限を持つアカウント)またはsudoコマンドを頭につけて行ってください。

gccの有無確認

gcc -v

バージョンが書いてあるところにHomebrewと書いてある場合、gccは入っているのでOpenMPも導入済です。
clangと書いてある場合、ややこしいですがgccは入っておらず、gccというコマンドでclangが代わりに起動するようになっているため、gccをインストールする必要があります。gccがインストールされればOpenMPも同時にインストール完了です。

gccをインストール

brew install gcc

Homebrew経由でgcc, g++の最新版をインストール。
brewコマンドがなければ以下のサイト上にあるコマンドをターミナルにコピペしてインストールしておいてください。
https://brew.sh/index_ja.html

gccが入ってはいるが古い場合は、

brew upgrade gcc

でgcc, g++を最新版にしておくと良いですが不要かもしれません。

gccにシンボリックを貼る

ls /usr/local/bin | grep gcc
ls /usr/local/bin | grep g++

まずはインストールしたgcc, g++のバージョン確認。例えばgcc-10g++-10と返されれば、先程インストールしたgcc, g++はバージョン10です。

ただこのままだとgcc-10と打たないとgccが起動しないので、gccと打てばgcc-10が起動するようショートカット(シンボリック)を作成します。

ln -s /usr/local/bin/gcc-10 /usr/local/bin/gcc
ln -s /usr/local/bin/g++-10 /usr/local/bin/g++

gccの優先順位確認

which -a gcc

gccというコマンドで起動するコンパイラを全て表示します。
/usr/local/bin/gcc/usr/bin/gccの二つが返されると思いますが、前者はgcc, 後者はclangです。
gccとコマンドを打った際にどちらが起動するか確認します。

which gcc

/usr/local/bin/gccと返されれば、gccが優先的に使用されるのでOpenMPの導入も完了です。OpenMPIの導入に移ってください。
/usr/bin/gccと返される場合、clangが優先的に使用されるのでgccを優先させる必要があります。

gccへの切り替え(パスを通す)

※パスはユーザーごとに異なるため、一般ユーザーであればスーパーユーザーからアカウントを切り替えて行ってください

gccを優先させるために、/usr/local/binにあるコマンドを優先させます(パスを通す)。
まずは自分が使用しているシェルを確認します。シェルによってパスの編集方法が若干変わってきます。

echo $SHELL

/bin/zshのように返されれば使用シェルはzsh、/bin/bashのように返されれば使用シェルはbashなので覚えておいてください(これら以外の可能性もあります)。

次にホーム(~)に移動してファイル一覧を表示し、自分のPCにパスの編集用ファイルがあるか確認します。

cd ~
ls -a

zshの場合はls -aで表示されたファイルに.zshrcがあればそれが編集用ファイルです。bashの場合は.bash_profileや.bashrcが編集用ファイルです。

編集用ファイルが無い場合は作成します(必ずホームで作成してください)。zshの方は、

touch .zshrc

bashの方は、

touch .bash_profile .bashrc

.bash_profileだけでも出来たりしますが今回は両方使用する設定を行います。

またbashの場合は事前準備として作成した.bash_profileに、以下を書いてください。これが無いと.bashrcが自動で読み込まれません(zshの場合は自動で読み込まれるので以下の操作は必要ありません)。既にこの記述がある場合はそのままで大丈夫です。

.bash_profile
source ~/.bashrc

次にファイルにパスを通す記述をします。 .zshrcまたは.bashrcを開き、以下を書いてください。
.zshrcまたは.bashrc
export PATH="/usr/local/bin:$PATH"

編集が終わったら、設定を読み込むためターミナルを再起動してください。

再確認

which gcc

/usr/local/bin/gccと返ってくればgccが優先されています(gcc -vでHomebrewと表示されるはずです)。OpenMPも同時に使用可能になっています。

OpenMPIの導入

※またスーパーユーザーに戻るか必要に応じてsudoコマンドを用いて作業してください

OpenMPIのインストール

http://www.open-mpi.org/software/ompi/
こちらから最新版のOpenMPIのバージョンを確認してください。今回は例としてバージョン4.0.5を使用します。

ダウンロードしたい場所へ移動しダウンロードします(サイトから直接落としても可)。

cd ~/Desktop
wget http://www.open-mpi.org/software/ompi/v4.0/downloads/openmpi-4.0.5.tar.gz

wgetコマンドが無い場合はbrew install wgetで入れておいてください。

次に落としたものを解凍します。

tar xzvf openmpi-4.0.5.tar.gz

これでOpenMPIのフォルダが自動で生成されるので、フォルダに移動し作業を進めます。

cd openmpi-4.0.5

lsコマンドでファイル一覧を見ればわかりますが、この時点ではMakefileがないので(Makefile.amなどは別物です)、作成する必要があります。

./configure CC=gcc CXX=g++ F77=gfortran FC=gfortran --enable-mpi-thread-multiple --prefix=/usr/local/bin

ここでCCはCのコンパイラ、CXXはC++のコンパイラ、F90, FCはfortranのコンパイラ、--prefixはインストール先のフォルダを指定します。
これを行うとMakefileなどが生成されます。
試していませんがfortranのコンパイラを入れておらず、fortranを使わないようであればF77, FCの指定はしなくて良いかもしれません。指定せずエラーが出た場合はfortranのコンパイラ(gfortranなど)も入れておいてください。

コンパイルとインストール

Makefileが生成されている事を確認できたら、コンパイルを行い、それをインストールします。

make
make install

どちらもかなり時間がかかります。
権限が無い場合はsudo make, sudo make install
またもしエラーなどが出てmakeをやり直す場合は、生成されたファイルを一度消した方が良いかもしれません(フォルダごと削除してダウンロードからやり直すと無難)。

コマンドの確認

OpenMPIを使用する際のコマンドはcとc++でそれぞれmpicc, mpic++です。

which mpicc
which mpic++

例えば/usr/local/binと返されれば、そこにmpicc, mpic++コマンドがあります。
パスをいくつか設定する必要があるため、実際にインストールされたフォルダの場所を調べます。
上記のwhichコマンドで返された場所へ移動し、OpenMPIの情報を見ます

cd /usr/local/bin
mpicc --version

Configured with:の欄に、--prefix=インストールされたフォルダが書いてあるので、その場所(例えば/usr/local/Cellar/gcc/10.2.0)をメモします。

パスの編集

※gccの時と同様に、一般ユーザーであればアカウントを切り替えて行ってください

gccのパス設定と同様に、ホームに移動しパス編集用ファイルを開き以下を記述します。

.zshrcまたは.bashrc
#openmpi
export MANPATH=インストールしたフォルダ/share/man:$MANPATH
export LD_LIBRARY_PATH=インストールしたフォルダ/lib:$LD_LIBRARY_PATH
export PATH="インストールしたフォルダ/bin:$PATH"

ここでのインストールしたフォルダは、先程mpicc --versionで確認したフォルダです。

編集が終わったら、設定を読み込むためターミナルを再起動してください。

確認

echo $PATH
echo $MANPATH
echo $LD_LIBRARY_PATH

先程パス編集ファイルに記述した場所が含まれていればパスの設定は成功です。
mpicc, mpic++コマンドが無い場所へ移動し(cd ~など)、

mpicc --version
mpic++ --version

これで反応があればパスは通っています。OpenMPIが使用可能です。

サンプルプログラム

https://mpitutorial.com/tutorials/mpi-hello-world/
こちらのMPIのサンプルプログラムに、OpenMPによる複数スレッドからの出力を追加しました。

touch sample.cpp
sample.cpp
#include <stdio.h>
#include <omp.h>
#include <mpi.h>

int main(){
  //初期化
  MPI_Init(NULL,NULL);

  //プロセス数(この計算全体で何コア使用しているか)の取得。実行時にコア数は指定できる。
  int world_size;
  MPI_Comm_size(MPI_COMM_WORLD, &world_size);

  //ランク(現在何番目のコアで計算しているか)を取得
  int world_rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

  //プロセッサー名を取得
  char processor_name[MPI_MAX_PROCESSOR_NAME];
  int name_len;
  MPI_Get_processor_name(processor_name, &name_len);

//OpenMPによる並列化
#pragma omp parallel
  {
    //ランクが0でスレッドが0の時、プロセス数と1プロセスあたりのスレッド数を出力
    if(world_rank==0 && omp_get_thread_num()==0){
      printf("the number of processors : %d\n", world_size);
      printf("the number of threads per 1 process : %d\n", omp_get_num_threads());
    }
//出力している間他のスレッドはここで待機。不要かも。
#pragma omp barrier
  }
  //出力している間他のコアはここで待機。無くても良い。
  MPI_Barrier(MPI_COMM_WORLD);

//各プロセス、各スレッドから出力
#pragma omp parallel
  {
    printf("Hello world from processor %s, thread %d, rank %d\n", processor_name, omp_get_thread_num(), world_rank);
  }

  //終了処理
  MPI_Finalize();

  return 0;
}

MPIによるコア並列処理はプログラム実行時に指定しますが、OpenMPによるスレッド並列処理はプログラム内に逐一記述します。

OpenMPIのコンパイルはc,c++でそれぞれmpicc, mpic++で行います。またOpenMPを有効にするにはコンパイルオプションで指定する必要があります。オプションはコンパイラによって異なりますが、gccの場合は-fopenmpです。

mpic++ -fopenmp sample.cpp

また使用するスレッド数(1プロセスあたり)はあらかじめ指定しておきます。指定する数字については下記の補足:コア数、スレッド数の上限を参照してください。
指定方法はbashやzshでは下記のようにexport OMP_NUM_THREADS=[スレッド数]ですが、使用シェルによって変わります。また毎回同じ数を指定するのであれば、.bashrcや.zshrcに書いておくことでいちいち書かなくて良くなります。

export OMP_NUM_THREADS=2

今回は例として2個に指定しました。

実行はmpirunで行います(試していませんがmpiexecでも出来るそうです)。

mpirun -np 4 ./a.out

-npまたは-nでOpenMPIで使用するコア数(プロセス数)を指定します。例として4個としています。

the number of processors : 4
the number of threads per 1 process : 2
Hello world from processor [プロセッサー名].local, thread 1, rank 0
Hello world from processor [プロセッサー名].local, thread 0, rank 0
Hello world from processor [プロセッサー名].local, thread 1, rank 1
Hello world from processor [プロセッサー名].local, thread 0, rank 1
Hello world from processor [プロセッサー名].local, thread 1, rank 2
Hello world from processor [プロセッサー名].local, thread 0, rank 2
Hello world from processor [プロセッサー名].local, thread 1, rank 3
Hello world from processor [プロセッサー名].local, thread 0, rank 3

のように一度の実行でプロセス数×1プロセスあたりのスレッド数の数だけHello worldが出力されていれば成功です。

補足:コア数、スレッド数の上限

使用できるコア数はPCによって異なります。
PCの全コア数の確認は、

system_profiler SPHardwareDataType

Total Number of Cores:の値が使用できる最大のコア数です。単一ノードの場合これ以上の数字は指定出来ません。

またスレッド数はいくらでも増やせますが、全スレッド数(1プロセスあたりのスレッド数×プロセス数)がPCの論理コア数を越えると一度に処理が出来ないので計算が遅くなります。 論理コア数の確認は、

sysctl -n hw.logicalcpu_max

また論理コア数を上回っていなくても、コア数とスレッド数は大きくしすぎるとデータを各スレッド、各コアに分ける作業や各スレッド、各コアから集める作業で時間がかかる可能性があるので注意してください。

参考文献

16
11
2

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
16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?