Mountain LionからのMavericksとの戦いの記録
山田です
ちまたじゃ関東GPGPU勉強会#3とかですが、それに向けて準備を進める最中、普段のコーディングマシンことMacbookPro Retina(Mid 2012)ちゃんをMavericksにアップデートしたらさぁ大変、MacPortsのアップデートが出来なくなりました
まぁ実際実害なかったんですけど、gccがらみで何かをやらかしたらしく、gcc4.7が動かなくなってさぁ大変。さりとてなぜか4.8を入れようとしたら頻発するエラー
この辺は色々Twitterに書きなぐったので今更ではありますが、備忘録として書いておきます
ただ、コレに関してはなんか銀の弾丸っぽいものがあって、それは以下
本体をMavericks版にしたあと一度全部消せ
いやそれどーなんって言われそうですが、llvm周辺の対応とか考えるに、昔のパッケージを残したままというのはトラブルのもとなので一回とばすのがよいかと
飛ばすときには今入れてあるパッケージのバックアップとか取れば良いと思うので
$ port installed > installed_list.txt
とかやればいいんじゃないですかね。多分。
あとは
$ sudo port clean --all all $ sudo port -fp uninstall installed
とかやれば色々吹っ飛ぶと思います
その上で入れ直しとかすると、なんかとても晴れやかな気持ちでMacPortsが動いています。今のところ!
という状態なんで、僕としては満足ですが、gcc4.8のビルドはよ終われ
XeonPhiハッカソン開催しました
山田です
八月頭から疲れが取れないとかでずるずるこんなタイミングになってしまいましたが、XeonPhiハッカソン開催しました
http://mic-hackathon.peatix.com/
前日から当日にかけてもうてんやわんやの大騒ぎで死ぬかと思いましたが、ひとまずは、開催できてよかったと安堵の気持ちでいっぱいです
ただ、いろいろと課題として考えなければいけないものが見えてきたとは思うので、今後(あるのか?)に活かしていければと思います
この時得られた成果に関しましては、後程共有いたしますので、お待ちください
…でなんか、XeonPhiハッカソン終わってもう二度とやらん、もうやらんとか言ってる僕のところにさっそく、「第三回やろーぜー」という連絡が届いたりしたのですが、そんなにネタないんですけど誰かご参加いただけませんかねえ…!
というわけで一つよろしくお願いします
できるXeonPhi(2) !
はじめに
XeonPhiを導入したりvecAddしたりしたけど、それでも使いこなすにはまだまだだよね!
ということで、最適化について書いていきたいと思います
…実際、
http://software.intel.com/en-us/articles/intel-cilk-plus-aobench-sample
のコード解説以上の何かではないので、あらかじめお断りしておきます
題材
取り上げる題材は、みんな大好きAobenchです
https://code.google.com/p/aobench/
ふぁーすとすてっぷ
まずはCPUでどれぐらいの速度が出ているのかを見ます
計測に使うPCは、
あと、サイトに乗っている条件だと軽そうなので、条件をもう少し厳しくして
#define WIDTH 512 #define HEIGHT 512 #define NSUBSAMPLES 2 #define NAO_SAMPLES 16
にします。
あと適当に時間計測用関数を仕込んで、とりあえずシングルコアで実行してみましょう。そーれー
$ icc Aobench.c -O3 -lm -o aobench $ ./aobench time : 12.181054 sec.
何一つ並列化しないで12秒でした
あとでHaswellで測ってみるか…
一応XeonPhi上のコア1コアでも測ってみると
$ icc Aobench.c -O3 -lm -mmic -o aobench_mic $ scp aobench_mic mic0:~/ <- SCPでmic0にデータ送って $ ssh mic0 <- mic0にログインして $ ./aobench_mic <- 実行 time : 126.206486 sec.
あぁ、なんかこういう数字、Cell/B.E.のコード書いてる時によく見た気がする…
さて、ではまずどこが一番重いのかを特定します
アムダールの法則的に、そこを重点的に潰していくのが一番いいのでス
とりあえず適当にVtuneで速度を見ました
ambient_occlusion関数が重いです
どんな時に呼ばれるかというと
void render(unsigned char *img, int w, int h, int nsubsamples) { ... for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { for (v = 0; v < nsubsamples; v++) { for (u = 0; u < nsubsamples; u++) { ... Isect isect; isect.t = 1.0e+17; isect.hit = 0; ray_sphere_intersect(&isect, &ray, &spheres[0]); ray_sphere_intersect(&isect, &ray, &spheres[1]); ray_sphere_intersect(&isect, &ray, &spheres[2]); ray_plane_intersect (&isect, &ray, &plane); if (isect.hit) { ambient_occlusion(&col, &isect); ... } } } } } }
width * height * sample * sampleの中で、飛ばしたレイがどこかにあたったときに計算するとそういうわけですね
# 言ってることが謎みたいな方は
# http://kagamin.net/hole/edupt/index.htm
# の物理ベースレンダラの資料を読んだりアンビエントオクルージョンについてググられるとそれなりに意味がわかるかと
まずambient_occlusion関数やるとか言った舌の根も乾かぬうちに、とりあえずこのループを何とかしてみましょう
ここで、上のループは全て独立であるということが一つ大きなポイントです
独立であるということは、それは別に並列で動作しても何も問題ないよね? ということで並列に動かしてみます
まぁ言うまでもないことですが、OpenMPを使うときのループ変数はきちんとスレッドローカルになるようにしてあげないと大変なことになるので、頑張って変えてください
そして、変えた結果がこちら
$ icc Aobench.c -O3 -lm -openmp -std=gnu99 -mmic -o aobench_mic $ scp aobench_mic mic0:~/ $ ssh mic0 $ ./aobench_mic time : 44.997415 sec.
さっくり3倍ぐらい
とはいえ、CPUのシングルスレッドにまだ4倍近い差をつけられて負けているので、このままだとXeonPhiの存在意義に関わるわけで、さらに最適化を推し進めましょう
せかんどすてっぷ
今度こそambient_occlusion関数を見ます
void ambient_occlusion(vec *col, const Isect *isect) { ... int ntheta = NAO_SAMPLES; int nphi = NAO_SAMPLES; ... real occlusion = 0.0; for (j = 0; j < ntheta; j++) { for (i = 0; i < nphi; i++) { real theta = sqrt(drand48()); real phi = 2.0 * M_PI * drand48(); real x = cos(phi) * theta; real y = sin(phi) * theta; real z = sqrt(1.0 - theta * theta); real rx = x * basis[0].x + y * basis[1].x + z * basis[2].x; real ry = x * basis[0].y + y * basis[1].y + z * basis[2].y; real rz = x * basis[0].z + y * basis[1].z + z * basis[2].z; Ray ray; ray.org = p; ray.dir.x = rx; ray.dir.y = ry; ray.dir.z = rz; Isect occIsect; occIsect.t = 1.0e+17; occIsect.hit = 0; ray_sphere_intersect(&occIsect, &ray, &spheres[0]); ray_sphere_intersect(&occIsect, &ray, &spheres[1]); ray_sphere_intersect(&occIsect, &ray, &spheres[2]); ray_plane_intersect (&occIsect, &ray, &plane); if (occIsect.hit) occlusion += 1.0; } } occlusion = (ntheta * nphi - occlusion) / (real)(ntheta * nphi); ... }
一度あたったところが、どれぐらい遮蔽されているか? というのを求めるのがこのコード
NAO_SAMPLE * NAO_SAMPLEの二乗のループになっています
今の条件だと、NAO_SAMPLE * NAO_SAMPLE = 16 * 16 = 256ですね
16とかすごくキリのいい数字なので、なんか並列化できそうですね。4の倍数とか2の倍数とかを見ると割とラッキーとかなります
でもIntrinsicsを直で書くのは面倒です…
そんな時はCilk+を使いましょう!
Cilk+を使うと何がイイか、というのは、下の記事を読めばよいかと思います
配列表記 (アレイ・ノーテーション) による SIMD 並列処理
http://www.isus.jp/article/parallel-special/simd-parallelism-using-array-notation/
コンパイラによる自動ベクトル化を行う上で必要になるあれこれをうまいこと何とかしてくれるいい感じのアノテーションです
どんなコードになったかは、ここに乗せるのは結構長くなってしまったので一部抜粋でお送りします。
詳細は後述のリポジトリのほうを見てください
void ambient_occlusion(vec *col, const Isect *isect) { ... real rand1[NAO_SAMPLES] __attribute__((aligned(64))); real rand2[NAO_SAMPLES] __attribute__((aligned(64))); real theta[NAO_SAMPLES] __attribute__((aligned(64))); real phi[NAO_SAMPLES] __attribute__((aligned(64))); real x[NAO_SAMPLES] __attribute__((aligned(64))); real y[NAO_SAMPLES] __attribute__((aligned(64))); real z[NAO_SAMPLES] __attribute__((aligned(64))); ... for(int j = 0; j < ntheta; ++j) { for(int k = 0; k < nphi; ++k) { rand1[k] = (real)drand48(); rand2[k] = (real)drand48(); } theta[:] = sqrt(rand1[:]); phi[:] = (real)2.0 * M_PI * rand2[:]; x[:] = cos(phi[:]) * theta[:]; y[:] = sin(phi[:]) * theta[:]; z[:] = sqrtf((real)1.0 - theta[:] * theta[:]); rx[:] = x[:] * basis[0].x + y[:] * basis[1].x + z[:] * basis[2].x; ry[:] = x[:] * basis[0].y + y[:] * basis[1].y + z[:] * basis[2].y; rz[:] = x[:] * basis[0].z + y[:] * basis[1].z + z[:] * basis[2].z; } .... }
real型はただfloatをtypedefしただけです
こうすると、確かにSIMD化されたコードが出力されます
確認、一応-Sオプションつけてコンパイルしてアセンブリ出力すると見えるんですが、ここに載せようとすると超長くなるのでやめます
さて、この状態だと?
$ ./aobench_cilkplus time = 23.9387 sec.
それでもまだCPUと比べると倍近いわけですね。さて、どうしたものか。
さーどすてっぷ
何が遅いんですかねぇ?
入念な調査の結果、遅い箇所がどこなのかを特定しました
# 実は関東GPGPU勉強会#2後、Haswell買いに行って始発を待つまでのファミレスの中で気が付いてその場でコードを書くというエクストリームなことをやりました
遅いのは、ここでした
for(int k = 0; k < nphi; ++k) { rand1[k] = (real)drand48(); rand2[k] = (real)drand48(); }
もっというと、drand48()でした
標準ライブラリのrand()とかは、XeonPhiではベクタライズされて速くなる、みたいなことを書いてあるのを見るのですが、そうでもない?ような感じです
ここで高速な乱数生成器であるXorShiftを導入してみます
XorShiftについてはググっていただきたいところですが、XORとビットシフトだけで構成された乱数生成器で、そこそこの周期でなかなか高速といった特徴を持ちます
コードについては、リポジトリのほうを参照してください
というわけで、XorShiftを導入した結果というのは
$ ./aobench_cilkplus time = 1.05928 sec.
以上のようになりました
まとめ
Aobenchを対象に、XeonPhiでの最適化についてちょっとだけ触れました
今回はIntelさんのAobenchのCilk+サンプルをちょっと改変してXeonPhiに載せて高速化していますが、Cilk+のアノテーションによるSIMD化は、実はそこまで効率が良いわけではないっぽいことを感じています
あと鬼門は標準ライブラリのrand()でした。最初全然気づきませんでした
XeonPhiではシングルスレッド比で約12倍の性能を達成しましたが、シングルスレッド比なのでフェアじゃないです
これをマルチスレッド化してSIMD化したらどうなるのか? それについては、今ここで触れると泣きたくなるのでやめましょう
というわけで、リポジトリは以下です
https://bitbucket.org/telmin/aobench_cilkplus/
お試しになられたい方はどうぞ
ちなみにXeonPhi固有のコードというのはないので、普通のマルチコアCPUでも動作します
ただしコンパイルにiccが必要です
# Linuxだと非商用であればフリーのがあります
できるXeonPhi!(0) そもそも導入どうやんの編
そういえば、導入ってどうやるか書いてないよね?ということに気が付いたので書きます
いや、端的に言って
http://software.intel.com/sites/default/files/article/335818/intel-xeon-phi-coprocessor-quick-start-developers-guide.pdf
http://registrationcenter.intel.com/irc_nas/3267/MPSS_Boot_Config_Guide.pdf
に書いてあることそのままだったりしますが…
インストール
まず、XeonPhiを使うためにはIntel Manycore Platform Software Stack、MPSSを導入する必要があります
http://software.intel.com/en-us/articles/intel-manycore-platform-software-stack-mpss
ここから自分のOSに見合うものを落としてきませう
当方の環境がRedHat 6.4だったので、それを使用します
# wget http://registrationcenter.intel.com/irc_nas/3267/mpss_gold_update_3-2.1.6720-15-rhel-6.4.tar
とかやると落ちてきますね。
アーカイブを取得したら、それを展開します
# tar xvf mpss_gold_update_3-2.1.6720-15-rhel-6.4.tar
展開ののち、展開したディレクトリ以下のすべてのrpmファイルをインストールします
# cd mpss_gold_update3 # yum localinstall *.rpm
このインストールが完了した時点で、mic向けの様々なコマンドがインストールされています
とりあえずXeonPhiを起動する準備は整いました
起動からログインまで
起動したい気持ちをぐっとこらえて、まずはログインする一般ユーザーで以下のコマンドを実行します
$ ssh-keygen
これはおなじみの認証鍵作成コマンドです。パスフレーズはお好きなのをご選択ください
ここで作った認証鍵を、XeonPhiのログインに使用します
# micctrl --initdefaults
このコマンドは、終わるまでにしばらく時間がかかるので辛抱強く待ちましょう
この時点で、XeonPhiにはホストと同じユーザー名を使用したユーザーが作成されています。ログインには、先ほど設定した認証鍵のパスフレーズを利用します
さて、認証鍵を作って、それをXeonPhiに認識させたら、いよいよXeonPhiを起動しましょう
# service mpss start
このコマンドで、MPSSがいよいよ起動し、連動してXeonPhiが起動します。しばらく時間がかかるので、これまた辛抱強く待ちましょう
さて、無事MPSSが起動したら、XeonPhiへのログインを行いましょう
マシン名はデフォルトではmic0になっています
$ ssh mic0
こうするのは普段通りですね。
さて、mic0からは応答として
$ ssh mic0 Enter passphrase for key '/home/telmin_orca/.ssh/id_rsa':
というのが返ってきていると思いますが、ここででてくるのが先ほど入力したパスフレーズで、ここにパスフレーズを入れるとXeonPhi上で動作しているLinuxにログインが可能です
ここまでで、XeonPhiにログインができました
はたしてXeonPhiにちゃんとログインできたかは、cpuinfoを見てみれば一目瞭然かと思いますので見てみましょう
$ cat /proc/cpuinfo | grep processor processor : 0 processor : 1 processor : 2 processor : 3 .......... processor : 236 processor : 237 processor : 238 processor : 239
はい、240スレッド見えていますね
まとめ
とりあえずXeonPhiが動くところまで持って行けました。
明日(というかもう今日だけども) は前回よりさらに最適化なネタを書くつもりでいるので、ご期待ください
予告:できるXeonPhi!(2)
Hello worldとvecAddでできる!とかなめてんの?って言われたんで、もう少しましな最適化の話を書きます
もちろん書いた内容のネタ被りはXeonPhiハッカソンに参加される方々にはご法度ですので、必読ですよフフフ
第一回XeonPhiハッカソンやります
山田です
なんか最近色々ブログで書こうとしたことを悉くやってない気がしますが、Twitterのほうで煽られると色々なことをやっている気がする今日この頃です
表題の通り、XeonPhiハッカソンやります
第一回XeonPhiハッカソン
http://mic-hackathon.peatix.com
今回は、XeonPhiで乗せたいアルゴリズムがある、走らせてみたいコードがある、自分のプログラムがどうXeonPhiに適応できるかを模索したいetc...
といった
「XeonPhiの活用可能性の模索」
を行いたいという方を対象としています。ハードル高いのはそのためです
僕らが望んでいる参加者としましては
・XeonPhiわからないけど、このアルゴリズムはきっとXeonPhiで速くなる予感がするから乗せてみたいけどそんな環境がない
・GPUでの速度はわかるけど、XeonPhiだとどうなるかわからないから乗せてみたい
・XeonPhi上でガチガチに最適化してみたい
といった方々です。
逆に
・よくわかんないけどXeonPhi触ってみたい
というような方は、今回は申し訳ないですが、エクセルソフトさんのセミナーに行ってください。あるいはインテル ソフトウェアカンファレンス。
有料かよ!!と思われるでしょうが、この300円は当日のおやつ代です。僕のところにはプラスを出さないことをお約束します
なんで今回こうしてお金を負担してもらうことにしたかというと、一つは勉強会という形式の参加率の微妙さにあります
もちろん予定は未定、一寸先は闇なのは重々承知してはいますが、それでも例を挙げると前回の関東GPGPU勉強会#2では、出席率は約6割でした
60人で埋まる会場で36人。このイマイチ加減が伝わるでしょうか?
せっかくATNDなりああいったイベントサービスの中で定員に入っている以上、補欠になった方のためにもきちんとお越しいただくことが最も重要であると考えます
#もっとも、ハッカソンの場合で、なおかつこのようにハードウェアの数が限定されるものの場合、人数が少ないことに越したことはないのですが
なので、お金を自分で出していればこないってコトも少なくなるだろう、というコトでこういう形になっています。
ご意見などあれば次回以降にフィードバックしていくつもりなのでぜひTwitterあたりで連絡ください。お待ちしてます
え?関東GPGPU勉強会#3?
9月まで待って