CodeZine(コードジン)

特集ページ一覧

インテルTBBから学ぶループの並列化

インテルTBBを通じて学ぶ並列処理(2)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2009/11/11 14:00
目次

ParallelReduceSampleの並列処理について

 ParallelReduceSampleはparallel_reduceテンプレートを使用しています。parallel_reduceテンプレートを使用するために、並列処理を行うオブジェクトは4つの条件を満たさねばなりません。

 1つ目は、専用のコピー・コンストラクター(インテルTBBでは「分割コンストラクター」と呼びます)を実装することです。普通のコピー・コンストラクタと区別するためにtbb::splitクラスを指定しなくてはなりません。

 2つ目は、デストラクターを用意することです。

 3つ目は、operator()を多重定義することです。

 4つ目は、joinというメソッドを定義しなくてはなりません。詳細は後でサンプルの解説と共に行います。

 ParallelReduceSampleの並列処理部分は下記の通りです。

ParallelReduceSampleの並列処理(Salesオブジェクトより抜粋)
/*-----------------------------------------------------------------
    並列的に分析値を算出するための設定です
----------------------------------------------------------------*/
//分割コンストラクター
Sales( Sales& obj, split ) : 
    name( obj.get_name() ), price( obj.get_price() ), 
    datas( obj.get_details() ), count( obj.get_detailCount() ), 
    sumAmount( 0 ), sumTax( 0 ), sumCount( 0 ),
    maxCount( INT_MIN ), maxIndex( -1 ), minCount( INT_MAX ), minIndex( -1 ), 
    aveCount( 0 ), aveAmount( 0 ){};
//計算結果を結合するためのメソッド
void join( const Sales& obj ) { 
    //合計を算出
    this->sumAmount += obj.get_sum(); 
    this->sumTax += obj.get_sumTax();
    this->sumCount += obj.get_countSum();
    //最大値と最小値を決定
    if ( this->maxCount < obj.get_maxCount() ) {
        this->maxCount = obj.get_maxCount();
        this->maxIndex = obj.get_maxIndex();
    }
    if ( this->minCount > obj.get_minCount() ) {
        this->minCount = obj.get_minCount();
        this->minIndex = obj.get_minIndex();
    }
};
//範囲内で各種計算を行います
void operator() ( const blocked_range<size_t>& range ) 
{
    SalesDetails* tmp = this->datas;
    for ( size_t i = range.begin(); i != range.end(); i++ ) {
        //合計を算出
        this->sumAmount += tmp[ i ].get_amount();
        this->sumTax += tmp[ i ].get_consumption_tax();
        this->sumCount += tmp[ i ].get_count();
        //最大値もしくは最小値を決定
        if ( this->maxCount < tmp[ i ].get_count() ) {
            this->maxCount = tmp[ i ].get_count();
            this->maxIndex = i;
        } else if ( this->minCount > tmp[ i ].get_count() ) {
            this->minCount = tmp[ i ].get_count();
            this->minIndex = i;
        }
    }
};

 分割コンストラクターでは、初期化リスト内で渡されたオブジェクトの任意の値をコピーし、分析によって導出する値(最大値など)については初期化しています。このコンストラクターは、parallel_reduceテンプレートがオブジェクトを分割する時に呼び出します。この時点ではまだ分析値(総売上、最大金額など)は導出されていませんので初期値に設定しています。

 operator()の多重定義では、売上金額・税金額・商品数の各項目を加算し、それと同時に最小値と最大値を割り出しています。この時点では、分割されたオブジェクト内での計算課程ですので、全体としての合計値や最大/最小値が割り出されているわけではないことと、今回はconstが指定されていないことに注意して下さい。このテンプレートでは変更が予想されますのでconstを定義してはなりません。ここは間違えやすい点ですので注意して下さい。

 全体としての各種値を出すのは、joinメソッドの仕事です。parallel_reduceテンプレートは最初に、対象となるオブジェクト(このサンプルではSales)を分割していきオブジェクトのコピーを作ります。分割が終わったらその範囲内で処理を行います。そして最後に、各コピーしたオブジェクトのjoinメソッドを呼び出して、2つのオブジェクトを結合していき、最終的に1つのオブジェクトになるまでそれを繰り返します。joinメソッドの引数が自分と同じオブジェクト(このサンプルではSales)なのはこれが理由です。

 ParallelReduceSampleを実行して下さい。並列処理の処理効率が良いことが確認できます。

 しかし、parallel_reduceテンプレートを使う上で注意するべき点があります。それは、演算の丸め誤差です。並列処理では処理の実行順序がバラバラで、割り算の計算結果が毎回違います。

 例えば、100/4/3/2を計算するとします。直列では毎回(((100/4)/3)/2)の順序で計算されます。しかし、並列では((100/4)/(3/2))の様に計算されます。従って、丸め誤差により計算結果が同じではなくなります。並列プログラミングを初めて行った際によく起こる過ちですので十分に注意して下さい。

 また、parallel_reduceテンプレートは結果を結合する処理がありますので、どうしてもparallel_forテンプレートよりも低くなります。ですから極力parallel_forテンプレートを利用するとよいでしょう。

並列ループの考え方

 今回2つのテンプレートを通じて、ループ処理を並列化する方法について解説しました。並列化に慣れていない人はあまりピンと来ないと思いますが、考え方そのものは非常に単純です。ループの処理は分解して考えると、次のように処理をしていることになります。

ループ処理の分解
//処理1
tmp[ 0 ].set_amount( this->price * tmp[ 0 ].get_count() );
tmp[ 0 ].set_consumption_tax( tmp[ 0 ].get_amount() * 0.05 );
tmp[ 0 ].set_total( tmp[ 0 ].get_amount() + tmp[ 0 ].get_consumption_tax() );

//処理2
tmp[ 1 ].set_amount( this->price * tmp[ 1 ].get_count() );
tmp[ 1 ].set_consumption_tax( tmp[ 1 ].get_amount() * 0.05 );
tmp[ 1 ].set_total( tmp[ 1 ].get_amount() + tmp[ 1 ].get_consumption_tax() );

//以下省略

 この処理をよく見ると、各々の処理が他の処理に依存せずに、限られた範囲内で処理をしています。ということは、マルチコアプロセッサならば、処理1と処理2が同時に行えると考えるのは自然です。これがparallel_forテンプレートの考え方です。

 次に、ParallelReduceSampleの様に要素全体で処理をする場合、タスクを分割してその範囲内で最大値などを求め、後でその結果を突き合わせて判断をするようにすれば、マルチコアプロセッサで同時に複数のタスクが処理できることになります。これが、parallel_reduceテンプレートの考え方です。

 この2つの考え方を改めて考えると、普段我々人間がしている仕事と同じです。私たちは多くの場合、1つのプロジェクトの作業を分割して複数の人間で作業しますし(parallel_forテンプレートと同じ)、各プログラマーが複数のオブジェクトをプログラミングしてからそれを統合して1つのシステムを実装します(parallel_reduceテンプレートと同じ)

 こうしてよく考えてみれば、並列プログラミングもそれ程特殊な考え方でないことが分かって頂けると思います。

まとめ

 今回は、インテルTBBのparallel_forテンプレートとparallel_reduceテンプレートの使い方を解説しました。また、その根底に流れる「ループ処理を並列化する」という考え方も解説しました。今まで並列プログラミングに対して苦手意識をもっている方は多いと思います。

 しかし、今回の記事を読めば、並列プログラミングの考え方がそれ程特殊なものではなく、普段我々人間がしていることと大差がないことが分かって頂けると思います。

 並列プログラミングは今後当たり前のものとなります。少しでもその準備のお手伝いができれば幸いです。次回は、より高度な並列プログラミングの概念を噛み砕いて解説します。お楽しみに。

参考資料

書籍

Webサイト



  • LINEで送る
  • このエントリーをはてなブックマークに追加

あなたにオススメ

著者プロフィール

  • インドリ(インドリ)

    分析・設計・実装なんでもありのフリーエンジニア。 ブログ「無差別に技術をついばむ鳥(http://indori.blog32.fc2.com/)」の作者です。 アドバイザーをしたり、システム開発したり、情報処理技術を研究したりと色々しています。 座右の銘は温故知新で、新旧関係なく必要だと考えた...

バックナンバー

連載:インテルTBBを通じて学ぶ並列処理
All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5