C++学習記(4): boost mpi (+openmpi)
ようやく本来の目的であった並列計算に。
最初はOpenMPによるお手軽並列計算を使ってみたのですが、メモリの共有の切り分けがうまくいかず、並列度を上げれば上げるほど遅くなっていく残念な結果に・・・
ということで、結局急がば回れ、MPIでちゃんと書くほうが確実。
準備
まずはOpneMPIをインストールして、続いてMPIを使いやすくしたboost mpiを利用するためにboostを(再)インストール。
情報源としては↓がいい感じです。
ただ、boost 1.61だとusing mpi ;がうごかないことが何度もあったので注意。こういう時は本家(Getting started - 1.61.0 )を見に行って、それに従うのが吉。
結局、.user-config.jamとproject-config.jamの両方で
using mpi : your/own/path/to/opnempi/bainaries ;
の形式の1文を追加して突破しました。
CMake
CMakeLists.txtは以下のような感じになりました。ただ、環境によって動かなかったりするのも経験したので、これを下敷きにしていろいろ試行錯誤するしかないみたいです。もっとスマートな書き方とかがあったら教えてください。
cmake_minimum_required(VERSION 2.8)
project(boostmpi_test)add_definitions("-Wall -std=c++11")
add_definitions("-O3")set( BOOST_ROOT "/usr/local/boost-1.61.0_gcc-4.9.2_mpi")
FIND_PACKAGE(Boost 1.6 COMPONENTS mpi serialization REQUIRED)
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR})
set( CMAKE_C_COMPILER mpicc )
set( CMAKE_CXX_COMPILER mpicxx )add_executable(helloBoostMPI helloBoostMPI.cpp)
add_executable(testScatter testScatter.cpp)
add_executable(testBroadcast testBroadcast.cpp)
add_executable(testGather testGather.cpp)TARGET_LINK_LIBRARIES(helloBoostMPI ${Boost_LIBRARIES} -lboost_mpi -lboost_serialization)
TARGET_LINK_LIBRARIES(testScatter ${Boost_LIBRARIES} -lboost_mpi -lboost_serialization)
TARGET_LINK_LIBRARIES(testBroadcast ${Boost_LIBRARIES} -lboost_mpi -lboost_serialization)
TARGET_LINK_LIBRARIES(testGather ${Boost_LIBRARIES} -lboost_mpi -lboost_serialization)
サンプル
せっかくなので、動作と使い方確認用に作成したソースを公開しておきます。
基本的に
を焼き直しただけなんですが・・・
C++ 学習記(3): vector
C++といえばvectorというくらい、配列は全部vectorにするのがいいっぽい。
どこで見たのか覚えていないのですが、webで見つけた資料を基に
vectorの挙動を調べるためのコードを書きました。ふーんって感じ。
#include <iostream>
#include <vector>
#include <string>
using namespace std;typedef vector<float> fvec;
void print(const char* str, vector<int> &v){
if(v.empty()){
cout << "コンテナ" << str << "は空です。" << endl;
cout << str << "の要素数 is " << v.size() << endl;
} else {
vector<int>::iterator it;
cout << str << "の要素数 is " << v.size() << endl;
for (it = v.begin(); it != v.end(); it++){
cout << " " << *it;
}
cout << endl;
}}
void print(const char* str, fvec &v){
if(v.empty()){
cout << "コンテナ" << str << "は空です。" << endl;
cout << str << "の要素数 is " << v.size() << endl;
} else {
fvec::iterator it;
cout << str << "の要素数 is " << v.size() << endl;
for (it = v.begin(); it != v.end(); it++){
cout << " " << *it;
}
cout << endl;
}}
int main(){
int i1;
vector<int> v1;
vector<int> v2(10, 3);
fvec fv1;
fvec fv2(5, 3.5);/* initalize */
cout << "*** initialize a vector ***" << endl;
print("v1", v1); // should be empty
print("fv2", fv2);
for(i1 = 0; i1 < 10; ++i1){
v1.push_back(i1);
fv1.push_back( (float)i1 * 1.1 );
}
cout << "# v1.push_back(i1);" << endl;
print("v1", v1);cout << "# fv1.push_back( (float)i1 * 1.1 );" << endl;
print("fv1", fv1);cout << endl;
cout << "*** clear a vector ***" << endl;
fv1.clear();
cout << "# fv1.clear()" << endl;
print("fv1", fv1);
/* vector of vectors */
cout << endl;
cout << "*** initialize a vector of vectors ***" << endl;
vector<fvec> vector_of_fv;
for(i1 = 0;i1 < 3; ++i1){
fvec new_fv(fv2);
vector_of_fv.push_back(new_fv);
}
// check if the above push_back is reference or deep copy
fv2[0] = -1.9;cout << "# fvec new_fv(fv2);" << endl;
cout << "# vector_of_fv.push_back(new_fv);" << endl;
print("fv2", fv2);
for(i1 = 0;i1 < 3; ++i1){
string str = "vector_of_fv[" + to_string(i1) + "]";
print(str.c_str(), vector_of_fv[i1]);
}for(i1 = 0;i1 < 3; ++i1){
vector_of_fv[i1][i1] = -1.2 * i1;
}
cout << "# vector_of_fv[i1][i1] = -1.2 * i1;" << endl;for(i1 = 0;i1 < 3; ++i1){
string str = "vector_of_fv[" + to_string(i1) + "](modified)";
print(str.c_str(), vector_of_fv[i1]);
}
}
出力結果
$ ./a.out [~/projects/CPP]
*** initialize a vector ***
コンテナv1は空です。
v1の要素数 is 0
fv2の要素数 is 5
3.5 3.5 3.5 3.5 3.5
# v1.push_back(i1);
v1の要素数 is 10
0 1 2 3 4 5 6 7 8 9
# fv1.push_back( (float)i1 * 1.1 );
fv1の要素数 is 10
0 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9*** clear a vector ***
# fv1.clear()
コンテナfv1は空です。
fv1の要素数 is 0*** initialize a vector of vectors ***
# fvec new_fv(fv2);
# vector_of_fv.push_back(new_fv);
fv2の要素数 is 5
-1.9 3.5 3.5 3.5 3.5
vector_of_fv[0]の要素数 is 5
3.5 3.5 3.5 3.5 3.5
vector_of_fv[1]の要素数 is 5
3.5 3.5 3.5 3.5 3.5
vector_of_fv[2]の要素数 is 5
3.5 3.5 3.5 3.5 3.5
# vector_of_fv[i1][i1] = -1.2 * i1;
vector_of_fv[0](modified)の要素数 is 5
-0 3.5 3.5 3.5 3.5
vector_of_fv[1](modified)の要素数 is 5
3.5 -1.2 3.5 3.5 3.5
vector_of_fv[2](modified)の要素数 is 5
3.5 3.5 -2.4 3.5 3.5
参考文献
完全にC++は未知だけどもJavaなら結構経験積んでいたので、私の知り合いの中で最もプログラミングに長けた大先輩におすすめしていただいた
を買いました。とりあえず、わからないことがあったらこれにあたってみると、1つくらいは例が載っているのでだいたい乗り切れています。*2
C++ 学習記(2): cmake
IDEが使えない可能性を考慮して、C++はlibtool chainかcmakeで何とかしようと思っていた。
で、とりあえずcmakeが簡単そうだったのでやってみる。
情報源は
Learning CMake <-- これ素晴らしい!
いま、cmake_testというディレクトリ内に
HelloCPPWorld.cppというC++ファイルだけが存在している。
このとき、最低限として以下の内容をCMakeLists.txtというファイルに書く。
HelloCPPWorld.cppを作っておく。
Add_definitions("-Wall -std=c++11")
Add_executable(Main HelloCPPWorld.cpp)
// HelloCPPWorld.cpp
#include <iostream>int main(){
std::cout << "Hello CodeLite World";
return 0;}
この状況で、
cmake . <-- makefileを生成
make
とするとMainという実行ファイルができる。
cmakeはヘッダファイル(hpp)の必要なものを指定したパスから買って見つけてくれるので、MakeFileより書くのが楽。
C++ 学習記(1): まずは環境確認整備
業務の都合でいままで書いたことのないC++を使わざるを得なくなった。
業務のほうは既存のC++コードを書き換える方向なので、まあなんとかなるだろうと思っているんだけど、スクラッチから起こすことができないので
makeの書き方も-Iや-Lオプションの意味もautomakeも何もわかっていない。
ということでやばいだろうということでよちよち勉強していくことにする。
やらないといけないであろうこと
MakeFileの読み方書き方、automake, cmake
コンパイルオプション
ldconfig, LD_LIBRARY_PATHの意味と設定方法を理解する
OpenMP / MPIによる並列化
参考文献
完全にC++は未知だけどもJavaなら結構経験積んでいたので、私の知り合いの中で最もプログラミングに長けた大先輩におすすめしていただいた
を買いました。とりあえず、わからないことがあったらこれにあたってみると、1つくらいは例が載っているのでだいたい乗り切れています。*1
設定
プログラム経験: Java(メイン、10年以上), Python(ここ2年ほどメインで使ってる)
OS: ubuntu14.04 on VMware
エディタ: emacs
IDE: JetBrains IntteliJ IDEAとPycharmがないとJavaもPythonもかけない
IDE絶対ほしいけど、JetBrainsのC++って有償なんだよね、確か・・・
現在の状況
何が入っているか確認するのもあってapt-get installでそれっぽいライブラリを指定してみる。
ちょっと探すと
という記事が見つかったので、載っているやつはインストールしておく。
Hello, world
まずはhello world.
(hello.cpp.cpp)
#include <iostream>
#include <cstdlib>int main(){
std::cout << "hello world" << std::endl;
return 0;
}
コンパイルして実行。
% g++ hello_cpp.cpp
% ./a.out
*1:const参照とか&, *の使い方とか私にとっては完全に悪夢なので、毎回見ています
Linux便利コマンドの覚書
コマンドラインでいろいろ文字列処理できるよっていうね。
常にpythonやperlさん使うより、覚えていたらずっと早かったりする。
そこで、便利だったコマンドを、実際の自分のusageに即してメモする。
というか100本ノックの第二章やってるだけです
cut
デフォルトでは、タブ区切りファイルからコラムを切り出せる。
tsvファイルがあったときに、その第1, 3コラムだけを抽出するには
cat hoge.tsv -f1,3
結果がほしいときは
cat hoge.tsv -f 1, 3 > output.tsv
とする。
区切り文字を変える時には-dをつける
など
sed
文字列変換。
で、hoge.txt内の"ABC"がすべて"DEF"に置換されてreplaced.txtに出てくる
paste
二つのファイルを、同じファイル行数ごとに連結する。
これ、実はすごく強力ではないか?入力ストリームを2つ同時に進めるってのは普通のプログラムだと難しいような気が(少なくとも perlの <>だけではできない?)
paste file1.txt file2.txt > file12.txt #concat with tab (default)
paste -d":" file1.txt fil2.txt > file12-colon.txt # concat with colon. -d to specify the connecting character
paste -d"ABC" fileq.txt file2.txt > file12-A,txt # どうも文字列の最初の1文字しか使わないようだ。この場合は"A"で連結される
sort
入力されたファイルを、ソートして並べ替える。
これまた神コマンドかもしれない。
例えば
高知県 江川崎 41 2013-08-12
埼玉県 熊谷 40.9 2007-08-16
岐阜県 多治見 40.9 2007-08-16
山形県 山形 40.8 1933-07-25
山梨県 甲府 40.7 2013-08-10
みたいな(タブ区切り)データがあったとき
sort -n -k3 data.txt # 第3列 (-k3)を、数値として(-n)、昇順にソート
大阪府 豊中 39.9 1994-08-08
岐阜県 美濃 40 2007-08-16
群馬県 前橋 40 2001-07-24
山形県 酒田 40.1 1978-08-03sort -n -k3 -r data.txt # -rで降順になる
山形県 酒田 40.1 1978-08-03
群馬県 前橋 40 2001-07-24
岐阜県 美濃 40 2007-08-16
大阪府 豊中 39.9 1994-08-08
これは圧倒的便利
cat
cat自体はまあ普通だけど、頭のよい使い方を
cat コマンド | コマンドの使い方(Linux) | hydroculのメモ
で見かけたので下記に引用する。
行数が膨大なテキストファイルに対してなにかの処理をしたい。
処理に時間がかかってしまうが、その “なにか” を処理させるワンライナーが正しいかどうか不安で何度か試行錯誤する必要がある場合に、試行錯誤中は、テキストファイル全体を処理するのではなく、
head
でファイルの一部だけを処理してみる。ワンライナーが完成したときに、コマンド履歴の
head
をcat
に書き換えるだけで処理を完成させることができる。“なにか” が仮に文字コード変換だとすると、こんな感じ。
## テキストファイルの文字コードがわからないが、とにかくUTF-8にしたい $ head huge.txt | nkf --guess CP932 (CRLF) ## CP932 というのをUTF-8にするにはどうしたらいいんだっけ? $ head huge.txt | nkf -sW ... ## 違った、文字化けしちまった、こっちかな? $ head huge.txt | nkf -Sw ... ## 合ってた。さて、改行コードはどうなってる? $ head huge.txt | nkf -Sw | nkf --guess UTF-8 (CRLF) ## 改行コードも直したい $ head huge.txt | nkf -Sw -Lu | nkf --guess UTF-8 (LF) ## OK。じゃあ、本番 $ cat huge.txt | nkf -Sw -Lu > result.txt
最後の本番のコマンドは
nkf -Sw -Lu huge.txt > result.txt
でもよいのだが、直前のコマンド履歴のhead
をcat
に書き換えるだけのほうが、タイピングが楽で間違えないのである。このときcat
はほとんど意味のある仕事をしないが、タイピングを楽にすることに意味がある。
screenの使い方!
実に10年ぶりくらいぶりにマシンをつけっぱなしにできない状況になったので、改めてscreenを再勉強。
screenの特徴は、一つのターミナル画面上で番号の切り替えで多数のセッションを作ることだけど、もう一つの特徴として「セッションはバックグラウンドで走っているのでそのターミナルを殺しても生き残る」というのがある。
ので、手元のマシンからsshで計算サーバにログイン、計算を回すという状態のときに
i) 計算サーバでscreenして新しい仮想ターミナルを作成、ログイン
ii ) 計算ジョブを投げる
iii) screenしたターミナルから抜ける
と、あとはsshが切れても計算サーバ上では ii) のジョブが生き残ってくれる。
で、後日改めて計算サーバ上でscreenしていたターミナルにログインすれば計算結果が回収できるというわけ。
portforwarding + autossh + sshfsと合わせて利用するので、ここで必要なコマンドを備忘録的にメモ。
新しいスクリーンを始める:
screen
スクリーンから離れる: detach from screen(殺さないで親ターミナルにもどる)
Ctrl-a d
入れ子でスクリーンするとわかりにくいので、新しくスクリーン作るときはいちいちスクリーンから離れて、親ターミナルに戻ったほうが良さげ。
今あるスクリーンを調べる: list screens
screen -ls
スクリーンのIDが並びます
指定のスクリーンに戻る: resume screen
screen -r ID
スクリーンを殺す
exit
ただのターミナルなのでね