Think Parallelで行こう!

第5回 タスク並列とデータ並列の違い

株式会社フィックスターズ
好田 剛介

2010/1/20

データ並列で実装してみる

 このプログラムをデータ並列で実装した場合、どのようになるでしょうか。

 データでの分割としては、チェック対象となる一連の奇数を等分する、という考え方ができます。

 各データには依存関係がないのでデータ並列によって並列化したのが次のコードです。

 

●prime_data_parallel.cxx
//#include <iostream>
//#include <iterator>
#include <vector>
#include <cmath>
#include <boost/thread.hpp>
#include <boost/progress.hpp>

class PrimeCalc
{
private:
    int m_start_index;
    int m_end_index;
    std::vector<int> & m_prime_vector;

public:
    PrimeCalc(int start_index, int end_index, std::vector<int> & prime_vector)
            : m_start_index(start_index), m_end_index(end_index),
                m_prime_vector(prime_vector) {}
    ~PrimeCalc() {}
    void operator () (void)
    {
        for (int i = m_start_index; i <= m_end_index; i += 2)
        {
            bool prime_flag = true;
            int square_root = static_cast <int> (sqrt(i));
            for (int j = 3; j <= square_root; j += 2)
            {
                if (0 == (i % j))
                {
                    prime_flag = false;
                    break;
                }
            }
            if (true == prime_flag)
            {
                m_prime_vector.push_back(i);
            }
        }
    }
};

int main(void)
{
    std::vector < int > v0;     /* prime vector */
    std::vector < int > v1;     /* prime vector */

    PrimeCalc pc0(3,   499999, v0);
    PrimeCalc pc1(500001, 999999, v1);

    {
        boost::progress_timer pt;

        boost::thread th0(pc0);
        boost::thread th1(pc1);

        th0.join();
        th1.join();
    }

    //std::copy(v0.begin(), v0.end(), std::ostream_iterator<int>(std::cout, " "));
    //std::copy(v1.begin(), v1.end(), std::ostream_iterator<int>(std::cout, " "));
    //std::cout << std::endl;

    return 0;
}

 prime_data_parallel.cxxという名前でファイルに保存し、コンパイルします。

$ g++ prime_data_parallel.cxx -o prime_data_parallel.elf -lboost_thread

 実行してみましょう。

$ ./prime_data_parallel.elf
0.96 s

 プログラムの解説です。

 main関数では、まず素数を判定した結果を保存するために、42行目で標準ライブラリのvectorクラスの変数を用意します。次に、45行目で素数の判定を行うPrimeCalcクラスの変数を、データ範囲と結果保存用vectorを引数にして定義します。そして、51行目でboost::threadにより新たなスレッドを生成して並列実行します。

 18行目のPrimeCalcクラスのoperator ()関数では、指定された範囲内にある各奇数が素数であるかを判定し、素数だった場合には結果を保存するためのvectorに詰めていきます。

 入力データは事前に用意されて変化せず、出力データはほかのスレッドとは完全に別の領域に出力するため、共有資源がないので特に排他制御を行っていません。そのため、この例ではキューから数字を取り出すときに排他制御を行うタスク並列に比べてオーバーヘッドがなく動作時間も短くなっています。

処理の流れを比べてみよう

 タスク並列とデータ並列に関する処理概要の流れ図を参考までに示します。

●タスク並列の流れ図
タスク並列の流れ図
●データ並列の流れ図
データ並列の流れ図

 今回は、マルチスレッドプログラミングの説明として分かりやすいように、このようなアルゴリズムとしましたが、素数を見つけるという目的に注目すれば「エラトステネスのふるい」のようなより効率の良いアルゴリズムもあります。

 実際には、並列化の前に、より良いアルゴリズムがないかという部分から検討を行う必要があります。

 次回は、ベクトル演算を使った高速化について解説します。


prev
3/3
 

Index
タスク並列とデータ並列の違い
  Page1
マルチスレッドプログラムの実際
分割と依存関係
並列化のためのアルゴリズム
  Page2
スレッドによる並列化
Page3
データ並列で実装してみる
処理の流れを比べてみよう
index Think Parallelで行こう!


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間