OpenMPなコンパイラ,MercuriumとNanos4の導入

前書き

どーもー! @laysakura です!
世の中には色んな Cコンパイラがありますね.かくいう自分も, Borland C Compiler から入り, gcc に挫折し, Visual C++ の cl にお世話になり,今は大抵 gcc を使っています.
そんな数あるコンパイラの中に, Mercurium というものがあります.
主に研究目的用のものらしいのですが,今後個人的にお世話になりそうなので記事にしますね.
公式のドキュメントや関連論文がいかにも研究目的っぽく,最初の取っかかりになりそうな文書がなさそうなので,お役に立てれば幸いです.
(※ Mercurium 単体のインストール法については丁寧な解説があるのですが,後述の Nanos4 を入れないと結局使えないのに, Nanos4 については評価論文くらいしかなかったりします)

Mercurium の概要

Mercurium は,The NANOS group が公開しているCコンパイラです.
しかしただのコンパイラではありません. Source-to-source コンパイラなのです.
gcc などの,いわゆる「ふつうの」コンパイラは,Cのソースファイルを入力とし,オブジェクトコード(*.o)や実行可能形式のコードを出力しますね.
それに対して,Mercurium はCのソースファイルを入力すると,Cのソースファイルを出力します.

別にアホの子な訳ではなく,Introduction to Mercurium (PDF) によると,

  • オブジェクトコードを吐くために,CPUアーキテクチャに固有なコードをたくさん書くのは嫌
  • もう他のコンパイラが優れた最適化を提供してるんだから,最適化は既存のコンパイラをバックエンドとして使うほうがいいじゃん

というお考えらしいです.

じゃあ Mercurium の何が嬉しいんだと言いますと,一つには OpenMP サポートがしっかりしている ということが挙げられると思います(というか自分の必要とするのがこの部分だけなのでそれ以外は知りません).
実は,gccの提供する OpenMP のランタイム libgomp は,他のコンパイラ(Intel C Compiler, Cilk, IBM XL Compiler など)に比べて非常に性能が出づらいようです.
特に, OpenMP 3.0 から登場した `task' 構文に関しては,他のコンパイラ達に大敗を喫しています.
(参考: Evaluating OpenMP 3.0 Run Time Systems on Unbalanced Task Graphs (PDF))

そこで,The NANOS group の提供する Nanos4 を libgomp の代わりに OpenMP ランタイムとして用いて, Mercurium+Nanos4 でコンパイルしてできたCのソースを,バックエンドの他のコンパイラ(gccなど)に食わせてやって,グレートに並列化された実行ファイルを得るわけです.
ここから, Mercurium コンパイラと Nanos4 ランタイムライブラリの導入について書いていきます.

Mercurium と Nanos4 の導入

Mercurium の導入

これに関しては,ここ にしっかりした解説がありますので,こちらを読んでください.
自分は, git を使って次のように導入しました. (git を使う場合は こちら の解説も参照してください)

git clone http://pm.bsc.es/git/mcxx.git
cd mcxx
autoreconf -v -i
./configure --prefix=/home/sho/software/mercurium --enable-tl-openmp-nanos4 --with-nanos4=/home/sho/software/mercurium
make   (もし失敗したら下を読んでください)
make install

makeの際, gcc 4.7.0 だとコンパイルエラーになりましたので,うるさいエラーを警告に変えます.
エディタで src/tl/Makefile を開き, CXXFLAGS の行に -fpermissive を追加します.

CXXFLAGS = -g -O2 -fpermissive

さて,とりあえずここまでで Mercurium コンパイラが手に入りました.この場合, /home/sho/software/bin に mcc という実行可能ファイルが入っています.これがコンパイラ本体です.
PATH を通さないと面倒なので,何とか通しましょう.自分は ~/.bashrc をこんな感じにしました.

export PATH=~/software/mercurium/bin:$PATH
export LD_LIBRARY_PATH=~/software/mercurium/lib:~/software/mercurium/lib/mcxx:$LD_LIBRARY_PATH

ついでにインストールされたライブラリ(OpenMPのランタイムではない)にもpathを通してます.やりましょう.

これで晴れて Mercurium が使えるようになったので,早速試してみましょう!

mcc any_c_file.c
-> cc1: 致命的なエラー: nth_mcc.h: No such file or directory

・・・自分はこれで涙目になりました

Nanos4 の導入

何が起きたのでしょうか?
`nth_mcc.h' というのは Nanos4 の中にあるインクルードファイルです.
Mercurium をインストールする際,

./configure --prefix=/home/sho/software/mercurium --enable-tl-openmp-nanos4 --with-nanos4=/home/sho/software/mercurium

という風に,「Nanos4を使うぜ!」って宣言しましたよね? だったら Nanos4 は当然入れてろよというわけみたいです.

じゃあ Nanos4 入れるかってことになるのですが,これも中々ひどくて 公式のダウンロードリンク がリンク切れ なんですね.ワロス
URL削って ここ を見つけました.ここにある最新版の nano-threads-library-.tar.bz2 をダウンロードしましょう.
あとはディレクトリ内の INSTALL をしっかり読んで,こんな感じに導入します.

tar xvf nano-threads-library-</span>.tar.bz2
cd nano-threads-library-*
./configure --prefix=/home/sho/software/mercurium CC=gcc CFLAGS=-O2
make
make install

Nanos4 をゲットしました!
ランタイムライブラリは,先程既にpathを通した ~/software/mercurium/lib に入ります.
新たに出来たインクルードファイル達にもpathを通すのを忘れずに.
自分は ~/.bashrc に

export INCLUDE=~/software/mercurium/include:$INCLUDE

を追加しました.

テスト

うまく導入できたかテストしてみましょう.
まずは Hello World ですね.
hello.c をこんな感じで作ります.

#include <stdio.h>

int main()
{
  printf("Hello, Mercurium!\n");
  return 0;
}

コンパイルして実行してみましょう.

mcc hello.c
./a.out
-> Hello, Mercurium!

うまくいってるみたいですね!
では, Hello WorldOpenMP版を試してみましょう.
omp.c をこんな感じで作ります.

#include <stdio.h>
#include <omp.h>

int main()
{
#pragma omp parallel
  {
    printf("Hello, Mercurium!\n");
  }
  return 0;
}

まずは gcc で試してみましょうか.

gcc -fopenmp omp.c -o omp-gcc
export OMP_NUM_THREADS=4   # OpenMPのスレッド数を決める環境変数
./omp-gcc
->
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!

では, mcc はどうでしょう?

mcc omp.c -o omp-mcc       # Mercurium は OpenMP使うのが当たり前(?)なので, -fopenmp 不要
export OMP_NUM_THREADS=4   # OpenMPのスレッド数を決める環境変数
./omp-mcc
->
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!

やりました! ばんざーい!!

導入が成功しているか最終検証

でも,本当に Nanos4 のランタイムが動いているのでしょうか? 実は gcc の libgomp 使ってるんじゃないの?
当然持つべき疑問ですね.調べてみましょう.
まず, omp-gcc が libgomp を参照していることを strace で確かめます.

strace ./omp-gcc 2>&1 |grep 'libgomp'
->

(libgompがないディレクトリを探索して失敗)
...
open("/usr/local/lib/libgomp.so.1", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320*\0\0004\0\0\0"..., 512) = 512

omp-gcc では libgomp を使っているみたいです. omp-mcc ではどうでしょう?

strace ./omp-mcc 2>&1 |grep 'libgomp'
-> (出力なし)

はい.使ってませんね.では omp-mcc が参照しているはずの Nanos4 ランタイムの実体もついでに確かめましょう.

strace ./omp-mcc
->
(たくさんの出力を目grep)
open("/home/sho/software/mercurium/lib/libnthreads-pfm-4.2.0.so", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260G\0\0004\0\0\0"..., 512) = 512

ありました! libnthreads-pfm というのを使っているみたいですね.
それでは最後に完全な安心感を得るとしましょう.

strace ./omp-gcc 2>&1 |grep 'libnthreads-pfm'
-> (出力なし)

これで今夜もぐっすりですね!!

最後に

なかなか丁寧な導入が書けたかと思いますが,いかがでしたか?
研究目的で Mercurium+Nanos4を使う際は, NANOS project の提供しているテストケース Barcelona OpenMP Task Suite (通称 BOTs) が約に立つと思われます.関連論文でも評価はこのテストケースを使って行われています.
快適なOpenMPライフをお楽しみくださいね!