Bag of ML Words

ML = Machine Learning, Music Love, and Miscellaneous things in daily Livings

C++学習記(4): boost mpi (+openmpi)

ようやく本来の目的であった並列計算に。

最初はOpenMPによるお手軽並列計算を使ってみたのですが、メモリの共有の切り分けがうまくいかず、並列度を上げれば上げるほど遅くなっていく残念な結果に・・・

 

ということで、結局急がば回れ、MPIでちゃんと書くほうが確実。

 

準備

まずはOpneMPIをインストールして、続いてMPIを使いやすくしたboost mpiを利用するためにboostを(再)インストール。

 

情報源としては↓がいい感じです。

kawa0810.hateblo.jp

 

ただ、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) 

 

サンプル

せっかくなので、動作と使い方確認用に作成したソースを公開しておきます。

github.com

 

基本的に

boostjp.github.io

を焼き直しただけなんですが・・・

C++ 学習記(3): vector

C++といえばvectorというくらい、配列は全部vectorにするのがいいっぽい。

 

どこで見たのか覚えていないのですが、webで見つけた資料を基に

vectorの挙動を調べるためのコードを書きました。ふーんって感じ。

*1

 

#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

*1:でも、まだ各要素にアクセスするときはfor(int i)にしちゃう(イテレータに慣れてない)

*2:const参照とか&, *の使い方とか私にとっては完全に悪夢なので、毎回見ています

C++ 学習記(2): cmake

IDEが使えない可能性を考慮して、C++はlibtool chainかcmakeで何とかしようと思っていた。

で、とりあえずcmakeが簡単そうだったのでやってみる。

 

情報源は

ごく簡単なcmakeの使い方 - Qiita

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がないとJavaPythonもかけない

IDE絶対ほしいけど、JetBrainsのC++って有償なんだよね、確か・・・

現在の状況

何が入っているか確認するのもあってapt-get installでそれっぽいライブラリを指定してみる。

 

f:id:Dr_KayAi:20160811231224p:plain

ちょっと探すと

bokko.hatenablog.com

 

という記事が見つかったので、載っているやつはインストールしておく。

 

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

Hello world.  

*1:const参照とか&, *の使い方とか私にとっては完全に悪夢なので、毎回見ています

Linux便利コマンドの覚書

コマンドラインでいろいろ文字列処理できるよっていうね。

常にpythonperlさん使うより、覚えていたらずっと早かったりする。

そこで、便利だったコマンドを、実際の自分のusageに即してメモする。

というか100本ノックの第二章やってるだけです

 

www.cl.ecei.tohoku.ac.jp

 

cut

デフォルトでは、タブ区切りファイルからコラムを切り出せる。

tsvファイルがあったときに、その第1, 3コラムだけを抽出するには

cat hoge.tsv -f1,3 

結果がほしいときは

cat hoge.tsv -f 1, 3 > output.tsv

とする。

 

区切り文字を変える時には-dをつける

cat hoge.csv -d',' -f2

など

 

sed

文字列変換。

sed -e 's/ABC/DEF/g' hoge.txt > replaced.txt

で、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-03

sort -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でファイルの一部だけを処理してみる。

ワンライナーが完成したときに、コマンド履歴の headcat に書き換えるだけで処理を完成させることができる。

“なにか” が仮に文字コード変換だとすると、こんな感じ。

## テキストファイルの文字コードがわからないが、とにかく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 でもよいのだが、直前のコマンド履歴のheadcat に書き換えるだけのほうが、タイピングが楽で間違えないのである。このとき 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

ただのターミナルなのでね