for のループカウンタがいじれて辛い問題

ぶんちょうさんが Twitter / yutopp: forループで
for(int i=0; ...
と おっしゃっていたので.

boost::for_each(boost::irange(begin, end), [](int i) { ...; }); な書き方は良いんですが,個人的にめんどくさいので for みたいに書けた方が良い.

ただループ変数を知らぬ間に辺なところで弄るので詰む.

explicit bool と operator T 使えばなんとなくいけるんじゃないですか.

Ideone.com - KxwKwb - Online C++0x Compiler & Debugging Tool

規格的に問題ない... と思う.

突っ込まれたし正論なので

for(auto const i : boost::irange(0, 10)) { ... } とかで良い.

政治的な理由等々により Boost の使用が禁止されている場合は boost::irange 自作とかで.

並列化の入門的な資料 (研究室向け)

研究室の大先輩が某所の*1某プロジェクトに入るそうなので,これまで並列化とか高速計算とかあまりやっていないけど,これからやらないとまずいってことだったので,僕が勉強会で教えていました.

昨日のうちに slideshare にアップロードしていましたけど言う予定がなかったのですが,liked されてたしまあ問題ないからアップロードしていたので一応記事に書いておきます.

内容としては,高速化の多分一番初歩的な話と,並列化する際の話,OpenMP と CilkPlus の話が書かれてます.タイトルがなぜか Parallel です.

プログラム高速化ことはじめ

注意・免責にも色々書いてあるのですが,結構大事なところを口頭でしか言ってない気がするので適宜補完してください...

後,OpenMP と CilkPlus の詳細は書いてなくて,スライドにも書いてありますが,この間の Boost.勉強会の資料等を参照してください.

CilkPlus, TBB, OpenMP

次は MPI (※Boost ではなく素の) のスライド書かないと...

*1:言って良いのかわからないので,どこかは濁しますが

Boost.勉強会 #11 で発表してきた

Boost.勉強会 #11 東京 - boostjp

茨城の山奥に行ったりとか,書類書いてたりとかして1週間も経ってしまいましたが書きます.

"CilkPlus, TBB, OpenMP 〜 並列プログラミングを支える技術" という大層なタイトルで発表してましたが,つまるところこの辺のライブラリとかコンパイラ拡張の紹介です.

発表スライド: CilkPlus, TBB, OpenMP

CilkPlus, TBB については以下のようなページがありますので,こちらを見ると良いと思います.

後は CG 法自体についての説明ですが... まあ興味ある方は調べてみてください.簡単に言うと線形代数で連立1次方程式を解くための解法です.

このスライドでは,ベクトル演算中心で並列化が容易だったので,それぞれのライブラリやコンパイラ拡張だけで差が出たら良いなあと思っていただけですので.

今回解いた問題で用いたのは Givens 行列という行列なんだそうです.

未知ベクトルを全て 1 として既知ベクトルを求めた後,未知ベクトルを全て 1 以外で初期化し,CG 法で解けば,未知ベクトルは 1 に近似された値が入る,となります.

ああ,後,行列ベクトル積のそれぞれのサンプルコードを載せて「簡単に書けますね」しか言わなかったのですが,もちろんご指摘がありましたように「簡単に書ける = パフォーマンスが良い」という風にはもちろん思ってないです.

実際にはパフォーマンスを調べてみないといけない訳ですが,一般的なアプリケーションにおいては簡単に書けて,かつスケーリングに強いというの方が良いと思っています.(もちろん,パフォーマンスが逐次計算時よりも良くなっているのが前提)

勉強会の感想とか

コンテナのパフォーマンスに関する発表では,会場に居ない人の代わりというわけでもなく色々指摘しましたけど,ちゃんと思ったことは言った方が良いですよ.

勉強会で発表してる人って,「僕何か間違ってないかな...」とか思いながら発表してる部分もきっとあると思います.(間違いに気づいてない人もいますけど) そういうのは直接言ってあげないと気がつかないと思います.僕もそんな感じなので.

懇親会も楽しかったです.ラズベリーパイとバイクと某氏の妹の話しか覚えてないですが.

#12 大阪は行きたいのですが,まあ色々予定が辛いので難しいですね...

追伸

闇鍋は夏が終わるまで無理です.暑いし.

CilkPlus について

コード書くの疲れたので休憩も兼ねて書きます.

知っている人も多いと思いますが,CilkPlus は並列処理を容易にするため,Intel が自社のコンパイラに用意している C/C++ 用の言語拡張です.
元々は MIT で作られていた C 言語用の拡張だったとかなんだとか.
まあそれはともかく,CilkPlus はキーワードが3つしかないとてもシンプルな設計です.

  • cilk_for
  • cilk_spawn
  • cilk_sync

の3つだけです.
後は reducer などが用意されています.

他には,ベクトル演算を簡略化する Array Notation,関数をベクタライズする Elemental Functions,ループのベクタライズを明示的に許可する #pragma simd などがあります.(これらも CilkPlus の一部だったのか...)

これらの定義は,全て にあります *1

CilkPlus のことについては以下のサイトが良いと思います.
Cilk Home Page | CilkPlus

利用できる環境

現在のところ,Intel コンパイラなら使用可能ですが,それ以外のコンパイラでは GCC は 4.8 で CilkPlus が使えるブランチがあり,4.9 では development branch があります.また LLVM/Clang でも実装されており,github に置かれています.詳しい話は以下.
Which License | CilkPlus

cilk_for

cilk_for は並列 for 文です.OpenMP だと pragma omp parallel for に該当します.

#include <cilk/cilk.h>

template<class T>
void add(std::vector<T> & out, std::vector<T> const& x, std::vector<T> const& y) {
  const std::size_t n = x.size();
  cilk_for(std::size_t i = 0 ; i < n ; ++i) {
    out[i] = x[i] + y[i];
  }
}

cilk_spawn, cilk_sync

cilk_spawn は関数を非同期実行させる命令で,cilk_sync は cilk_spawn で実行した全ての処理が完了するのを待機します.

#include <cilk/cilk.h>

int fib(int n)
{
    if (n < 2) {
        return n;
    }
    int x = cilk_spawn fib(n-1);
    int y = fib(n-2);
    cilk_sync;
    return x + y;
}

reducer

reducer は並行プログラミングでは非常に困る,複数のスレッド間で同期しなければならない計算を助けるための機能です.
例えば総和計算の関数は,全スレッドが参照する総和結果を返す変数をロックしなければいけませんが,reducer はそのようなロックをうまく隠してくれます.

#include <cilk/cilk.h>

template<class T>
T sum(std::vector<T> const& x) {
  cilk::reducer_opadd<T> result; // 自動でゼロ初期化されているらしい.
  cilk_for(std::size_t i = 0 ; i < x.size() ; ++i) {
    result += x[i];
  }
  return result.get_value();
}

終わり

OpenMP と比べると,#ifdef とかない分書きやすいと思います.
CilkPlus はオプションの指定が必要ないため,使える環境ならどこでも使えます.
後,OpenMP ではリダクションの指定が色々制限が強いのですが,CilkPlus なら reducer を自前で実装することで,ユーザ定義型でも使用できるようです.
Tutorial Cilk Plus Reducers | CilkPlus

そういえば OpenMP と CilkPlus の比較記事をあまり見たことがない.
どこかでやっているような気はするんですが...

*1:cilk_for, cilk_spawn, cilk_sync は実際にはマクロで,実際のキーワードは _Cilk_for, _Cilk_spawn, _Cilk_sync なのですが,cilk_for, cilk_spawn, cilk_sync を使うのが推奨されています

PLplot でコマンドライン引数にデータファイルを指定する方法

最近 TeX に載せるグラフを書くために Numbers とか使って EPS に変換したりとかしてたのですが, よくある2段組み用に使うと汚くてかなり見づらい.

今日とうとう諦めて PLplot を使って描画プログラムを書くことにしました.

PLplot Home Page - Main

PLplot は pgplot みたいなグラフ生成用のミドルウェアです.

バインディングは C, C++, Python, Fortran など.

EPS で出力すると結構奇麗な表示になって大変良いと思います.

ただ C++ の example が大変残念だったり, 凡例表示用関数が引数30超えてるとか*1まあ色々不満はあるのですが, ベタで書くよりは楽だろうと思います.

僕はいちいちデータファイルを変更するためにコンパイルし直したくないのでコマンドライン引数に渡したかったのですが, PLplot にオプション解析を乗っ取られているのでどうすれば良いのかと思ってたのですが, なんとなく調べても出てこなかったので一応メモ.

plparseopts 関数が引数を解析しますが, サンプルだと PL_PARSE_FULL だけが渡されているのが多いと思います.

ここに | で PL_PARSE_SKIP を指定すれば PLplot が解析できない引数をスキップできます. スキップしないとエラー出力で落ちます.

plparseopts 関数を通った後, argc, argv の内容は変更され, PLplot が認識した引数は全て削除されます. なので PLplot のオプションだけを渡せば, argc は 1 になります.

グラフに使用するデータファイルをコマンドライン引数に指定させようとすると, こういう書き方になります.

int main(int argc, const char** argv) {
  plparseopts(&argc, argv, PL_PARSE_FULL | PL_PARSE_SKIP);

  if(argc != 2) {
    std::fprintf(stderr, "[Usage] %s <PLplot options...> data-file\n", argv[0]);
    return -1;
  }

  const std::string dataFile = argv[1];

  ...
}

PLplot はちゃんと設定すれば Numbers で描画したグラフと遜色ないぐらい奇麗なグラフを EPS で出力できるので, TeX 使う人には便利だと思います.

Ruby のバインドがあれば良いなあとは思いました.

*1:[http://plplot.sourceforge.net/docbook-manual/plplot-html-5.9.9/pllegend.html:title]