Think Parallelで行こう!

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

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

2010/1/20

スレッドによる並列化

 それではスレッドによる並列化の例として、実際に素数の判定を行うプログラムを並列化して行きます。

 素数の判定方法にも色々なやり方があるとは思いますが、ここでは単純に3以上√n以下の奇数で、nが割りきれなかったら素数と判断することにします。これを3から99999までのすべての奇数に対して判断を行います。

 このプログラムでは、1つ1つの奇数について素数であるかどうか判断するというタスクが複数ある、というタスク分割の考え方ができます。各タスクには依存関係がないのでタスク並列によって並列化したのが次のようなコードです。

●shared_queue.hxx
#ifndef SHARED_QUEUE_HXX
#define SHARED_QUEUE_HXX

#include <queue>
#include <boost/thread.hpp>

template < typename T >
class SharedQueue
{
private:
    std::queue < T > m_que;
    boost::mutex m_mtx;
    boost::condition_variable m_not_empty;

public:
    void enqueue(const T & x)
    {
        {
            boost::lock_guard < boost::mutex > lock(m_mtx);
            m_que.push(x);
        }
        m_not_empty.notify_one();
    }

    T dequeue(void)
    {
        boost::unique_lock < boost::mutex > lock(m_mtx);
        while (m_que.empty()) m_not_empty.wait(lock);
        T x = m_que.front();
        m_que.pop();
        return x;
    }
};

#endif /* SHARED_QUEUE_HXX */
●prime_task_parallel.cxx
//#include <iostream>
//#include <iterator>
#include <vector>
#include <cmath>
#include <boost/thread.hpp>
#include <boost/progress.hpp>
#include "shared_queue.hxx"

class PrimeCalc
{
private:
    std::vector<int> & m_prime_vector;
    SharedQueue<int> & m_shared_queue;

public:
    PrimeCalc(std::vector<int> & prime_vector, SharedQueue<int> & shared_queue)
            : m_prime_vector(prime_vector), m_shared_queue(shared_queue) {}
    void operator () (void)
    {
        while (true)
        {
            int n = m_shared_queue.dequeue();
            if (0 == n) break;
            bool prime_flag = true;
            int square_root = static_cast <int> (sqrt(n));
            for (int j = 3; j <= square_root; j += 2)
            {
                if (0 == (n % j))
                {
                    prime_flag = false;
                    break;
                }
            }
            if (true == prime_flag)
            {
                m_prime_vector.push_back(n);
            }
        }
    }
};

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

    for (int i = 3; i <= 999999; i += 2)
    {
        sq.enqueue(i);
    }
    sq.enqueue(0);
    sq.enqueue(0);

    PrimeCalc pc0(v0, sq);
    PrimeCalc pc1(v1, sq);

    {
        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;
}

 それぞれshared_queue.hxxとprime_task_parallel.cxxという名前でファイルに保存し、コンパイルします。

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

 実行してみましょう。

$ ./prime_task_parallel.elf
1.15 s

 このプログラムの解説です。

 main関数では、まず素数を判定した結果を保存するために、prime_task_parallel.cxxの43行目で標準ライブラリのvectorクラスの変数を用意し、タスクが処理するべきデータを保存するために45行目でキューを用意します。

 ここで使用するキューは、標準ライブラリのqueueクラスを使い、それに排他制御を追加したものをshared_queue.hxxで独自にSharedQueueクラスとして定義しています。

 次に、47行目のforループで、そのキューに対して素数の判定に使用する奇数を追加します。

 54行目では、PrimeCalcクラスの変数を、結果を保存するvectorとタスクを受け取るキューを引数にして定義します。実際の素数の判定はこのPrimeCalcクラスによって行われます。そして、60行目でboost::threadクラスの変数にPrimeCalcクラスの変数を渡して定義することで、新たなスレッドを生成して並列実行します。

 16行目のPrimeCalcクラスのoperater ()関数では、キューから1つずつ奇数を取り出して素数かどうか判定し、素数だった場合には結果を保存するためのvectorに詰めていきます。

prev
2/3
next

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

index Think Parallelで行こう!


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

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間