No Such Blog or Diary

«Prev || 1 | 2 | 3 | 4 | 5 |...| 12 | 13 | 14 || Next»

libstdc++ parallel mode を試す

EuroPar2007で発表のあった The Multi-Core Standard Template Library (MCSTL) が GCC に統合されている(されつつある?)らしいので,セミナーのネタにと試用してみた.GCCのオフィシャルページを見ると libstdc++ parallel mode という名前で統合中らしい.こいつは既存の STL を OpenMP による並列実装の STL に置換しましょうというもので,既存の STL で書かれたコードがそのままで並列プログラムになってくれる.

んで,試そうと思ったら最新のGCCリリースであるところの 4.2.2 にも入っていないということに GCC4.2.2 のビルドが終わった時点で気づき… しょうがないので svn のリポジトリから最新のソースを落としてきて GCC をビルドした(8コアくらいあるマシンで make -j8 とかやるとビルドがとても速い).とりあえずソースの状態で libstdc++-v3/include/parallel というディレクトリがあれば parallel mode が使える.

準備できたので次の std::sort を使ったプログラムを試してみた.

#include <iostream>
#include <algorithm>
#include <vector>
const int count = 1000000, num_tests = 10;
int main(int argc, char **argv)
{
    std::vector<int> v(count);
    for (int i = 0; i < num_tests; i++)
    {
        std::generate(v.begin(), v.end(), rand);
        std::sort(v.begin(), v.end());
        std::cout << "." << std::flush;
    }
}

コンパイルは parallel mode 用のフラグ(-D_GLIBCXX_PARALLEL -fopenmp)を立てればよいらしい.

 g++-XXX test.cpp -o testS -march=native  # sequential mode
 g++-XXX test.cpp -o testP -march=native -D_GLIBCXX_PARALLEL -fopenmp  # parallel mode

Quad の Xeon x2 という8コアで動かした結果は以下のとおり.

> OMP_NUM_THREADS=8 time ./testS   # sequential mode
..........        6.26 real         6.24 user         0.01 sys
> OMP_NUM_THREADS=8 time ./testP   # parallel mode
..........        1.39 real         5.66 user         0.75 sys

普通に4~5倍速くなってるのを確認.すげー.並列プログラミング簡単.とりあえず sort は簡単に並列に動いてくれた.

ついでに, parallel mode は他にも並列実装を持っているらしいので試してみた.

まず std::partial_sum.4.51秒が1.65秒になった.8コアで約四倍.

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
struct one {
  int operator()() const {
    return 1;
  }
};
const int count = 10000000, num_tests = 10;
int main(int argc, char **argv)
{
    std::vector<int> v(count);
    std::vector<int> r(count);
    std::generate(v.begin(), v.end(), one());
    for (int i = 0; i < num_tests; i++)
    {
        std::partial_sum(v.begin(), v.end(), r.begin());
        std::cout << "." << std::flush;
    }
}

次は std::accumulate.3.20秒が3.79秒に増えた.全要素の和をとるだけなので普通に考えると8コアで8倍いきそうだけど… 速くならない理由は不明.

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
struct one {
  int operator()() const {
    return 1;
  }
};
const int count = 10000000, num_tests = 10;
int main(int argc, char **argv)
{
    std::vector<int> v(count);
    std::generate(v.begin(), v.end(), one());
    for (int i = 0; i < num_tests; i++)
    {
        int sum = std::accumulate(v.begin(), v.end(), 0);
        std::cout << "." << std::flush;
    }
}

最後に試したのは std::for_each.3.81秒が3.75秒.全要素に独立に関数適用するだけなので8コアで8倍いきそうだけど… 速くならない理由は不明.

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
struct f {
  void operator()(int &x) const {
    x = x + 1;
  }
};
struct one {
  int operator()() const {
    return 1;
  }
};
const int count = 10000000, num_tests = 10;
int main(int argc, char **argv)
{
    std::vector<int> v(count);
    std::generate(v.begin(), v.end(), one());
    for (int i = 0; i < num_tests; i++)
    {
        std::for_each(v.begin(), v.end(), f());
        std::cout << "." << std::flush;
    }
}

ということで,簡単に並列化できそうな部分でいろいろと失敗した模様.使い方が悪いのか実装途中なのか? よくわからんけどうまく動いた部分に関しては効果が確認できたのでよし.

gcc の OpenMP で遊ぶ

とりあえずやってみることその一.演算子のオーバロードがダメと言われても試したくなるのが人間.

struct DOUBLE {
    double x;
    double y;
    DOUBLE() : x(0), y(0) { }
    DOUBLE(int d) {
        x = d;
        y = d;
    }
    DOUBLE operator+(const DOUBLE& a) const {
        DOUBLE z;
        z.x = x + a.x;
        z.y = y + a.y;
        return z;
    }
};
DOUBLE a1(int n, DOUBLE *a)
{
    int i;
    DOUBLE r = 0;
#pragma omp parallel for reduction(+:r)
    for(i = 0; i < n; i++) {
        r = r + a[i];
    }
    return r;
}

コンパイルしたら

 error: 'r' has invalid type for 'reduction' 

と文句を言われた.だめらしい.

次,中途半端にオーバーロードしてみる.

struct DOUBLE {
        double x;
        double y;
        DOUBLE() : x(0), y(0) { }
        DOUBLE(int d) {
                x = d;
                y = d;
        }
};
 
double operator+(double a, DOUBLE b) 
{
    return b.x - a;
}
 
double a1(int n, DOUBLE *a)
{
    int i;
    double r = 0;
#pragma omp parallel for reduction(+:r)
    for(i = 0; i < n; i++) {
        r = r + a[i];
    }
    return r;
}

コンパイルしても文句言われない.でも演算子の結合性がないので当然ながら結果はおかしい.各スレッドでの結果をマージする部分では通常の + になってしまうので当り前だけど.

ついでに,次の無意味なオーバーロードは正しい計算がされる.

double operator+(double a, DOUBLE b) 
{
    return a - b.x;
}

結局,全要素に - を map して + で reducction するだけだから.

結論:reduction に演算子オーバーロードはやっぱり使えなかった.でもこうなると行列を for 文で掛けまくるとかいう操作は並列化してくれないのかぁ.行列積自体は結合的だけど展開した式での各要素は reduction の式にならないし.使えん.

gcc4.2 で OpenMP 使ってみた

今更ながらだけど gcc4.2 で OpenMP 使えるのを試してみた.使うにはふつうに OpenMP のディレクティブ書いて -fopenmp つけてコンパイルするだけ.あとは OMP_NUM_THREADS 環境変数にスレッド数をぶち込んで動かす.

で,parallel for とか reduction(+:r) とかで並列実行されるのを確認.ただ,reduction で本来の計算以外に結構時間取られてるっぽいので要調査かな.

あと,OpenMP 2.5 の仕様をみると reduction のオペレータはオーバーロード禁止かついわゆる C or C++ の演算子しか使えないとあるのでgccでもそうなのか試してみよう.オーバーロードくらい許してくれないと何にも出来ん.

g++ のインライン展開

どうやら g++ は varargs な関数を展開できないらしい.

#include <iostream>
 
struct h {};
struct k : h {};
 
namespace g {
  inline bool f(...) { return false; }
  inline bool f(h* ) { return true;  }
}
namespace v {
  template <typename A>
  inline bool f(A)   { return false; }
  inline bool f(h* ) { return true;  }
}
 
int main(int argc, char *argv[])
{
  h *ph; k *pk; int *pi;
  std::cout << g::f(pi) << std::endl;
  std::cout << g::f(ph) << std::endl;
  std::cout << g::f(pk) << std::endl;
  std::cout << v::f(pi) << std::endl;
  std::cout << v::f(ph) << std::endl;
  std::cout << v::f(pk) << std::endl;
}

g::f(pi) は inline bool f(int* ) なので展開される.g::f(h()) は inline bool f(...) なので展開されない.他は全部展開.ついでに,(...) を使わずに単純にテンプレート変数を一つ用意すればいいかなと思ったけど(...)とテンプレートを用いた場合とでオーバーロードの解決優先度が違うのでだめらしい.

古いソースだとbool boost::detail::function::has_empty_targetはvarargsを使ってないのになぜ最近の実装では varargs を使うようになったのだろう?

boost::function で…

インライン展開失敗するなぁ.

#include <iostream>
#include <boost/function.hpp>
 
struct F_t {
  double operator()(const double & x) const { return 1.5 * x; }
} F;
 
inline double G(const double & x) { return 1.5 * x; }
 
int main(int argc, char *argv[])
{
  boost::function <double(const double &)> f = F;
  boost::function <double(const double &)> g = G;
  std::cout << f(2.0) << std::endl;
  std::cout << g(2.0) << std::endl;
}

ただの関数 g の場合はインライン展開されるけど関数オブジェクト f のときは bool boost::detail::function::has_empty_target(...) が展開できないといわれる.さてどうしたものか?

なにがわるいのやら?

GCC の 4.X 系列を使いたいのでコンパイルしたら 5回連続でブルースクリーン… 悪名名高いウイルスバスターを疑いやつを止めてみてもブルースクリーン.cygwin が古めなのが悪いのかと最新のにアップデートしてもブルースクリーン.GCC のバージョンがいけないのかと 4.1.1 と 4.2.1 を試すもブルースクリーン.結局何が悪いのかさっぱりわからんけどオプション変えてまたコンパイルしよう.

«Prev || 1 | 2 | 3 | 4 | 5 |...| 12 | 13 | 14 || Next»
Search
Feeds

Page Top