Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

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

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

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

 本連載では、C++プログラムで並列処理を行うアプローチを提供するテンプレートライブラリ「インテルスレッディング・ビルディング・ブロック」(インテルTBB)について解説します。今回はインテルTBBのアルゴリズムテンプレートとループの並列化を説明します。

目次

はじめに

 この連載では、並列処理を高度に抽象化したインテルTBBを通じて、並列化の考え方を取得することを目的としています。今後、並列化は当たり前のものとなり、さまざまな形でサポートされるようになります。並列化処理の根底に流れる考え方を身につければ、その変化に対応できます。

 今回はインテルTBBのアルゴリズムテンプレートとループの並列化について解説します。この連載のサンプルはあくまでもインテルTBBの使い方を説明するものであり、実務を特別に意識したものではありません。その点をご理解下さい。

対象読者

 筆者が想定している読者はC++の基本的文法を理解し、並列化プログラミングに興味を持っている方です。高度なC++テクニックを極力さけ、基本的な文法さえ分かれば読めるように極力注意しますので、並列化に興味を持っている方はぜひこの連載に目を通して下さい。

必要な環境

 C++コンパイラが必要です。お持ちでない方は無償で提供されているマイクロソフト社の「Visual Studio C++ 2008 Express Edition」をダウンロードするなどして入手して下さい。

 この連載は基本的にWindows環境を想定して解説しますが、インテルTBBそのものは他のOS上でも動作しますので、適宜読み替えて参考にして下さい。

 その他に、インテルTBBを使うための準備が必要ですので次項で解説します。

インテルTBBの準備

 インテルTBBを使用する方法は2つあります。1つ目は「インテル Parallel Studio」を使用することです。インテル Parallel StudioはインテルTBBがあらかじめ用意されており、オプションを指定するだけでインテルTBBを使用することができます。詳しくは『インテル Parallel Studioを使って並列化プログラミングを試してみた』(CodeZine)を参照して下さい。

 2つ目の方法は、オープンソース版のインテルTBBを公式ホームページからダウンロードしてきて、ライブラリとインクルードファイルへのパスを指定することです。これからその手順を解説します。ただし、この記事は2009年10月に書かれたものであり、今後URLが変更される恐れがありますのでその点に注意して下さい。

 まずは公式ホームページにアクセスし、「Downloads」と書かれたタブを選択します。そして、「Stable Release」という見出しをクリックすると、インテルTBBのいくつかのバージョンが表示されます。

 ここでは「tbb22_20090809oss」を選択し、クリックするものとして話を進めます。画面下部の「tbb22_20090809oss_win.zip」と書かれた右横にある[Download]リンクをクリックすると、ダウンロードが始まります。ダウンロード後は、好きな場所に解凍して下さい。

 続いてVisual Studio(以下、VS)でパスの設定を行います。[ツール]-[オプション]-[プロジェクトおよびソリューション]-[VC++ディレクトリィ]を選択すると、「ディレクトリィを表示するプロジェクト」という項目があるので、そこでインクルードファイルを選択してから、先ほどダウンロードしたインテルTBBのパスを追加します。例えば、Cドライブに解凍したと仮定するとパスは「C:\tbb22_20090809oss\include」になります。このパスを追加したら、同様にして「ライブラリ」と「ソースコード」のパスを追加して下さい。

 以上で設定は完了です。これでインテルTBBを使う準備が整いました。

parallel_forについて

 インテルTBBで一番理解しやすく使いやすいのはparallel_forテンプレートです。まずはサンプルを掲載しますので、使い方を見て下さい。後から丁寧にparallel_forテンプレートについて説明を行います。

ParallelForSample
#include <time.h>   
#include <limits.h>
#include <iostream>
#include <vector>
#include <windows.h>
#include <winnt.h>
#include <tchar.h>
#include "tbb/parallel_for.h"
#include "tbb/blocked_range.h"
#include "tbb/task_scheduler_init.h"
#include "tbb/tick_count.h"

using namespace std;
using namespace tbb;

/*----------売上明細----------*/
struct SalesDetails {
private:
    int count; //数量
    double consumption_tax; //消費税額
    double amount; //合計金額
    double total; //売上+税金
public:

    //コンストラクタ
    SalesDetails() 
        : count( 0 ), consumption_tax( 0 ), amount( 0 ), total( 0 ) {};
    
    //セッター
    void set_count( int count ) { this->count = count; };
    void set_consumption_tax( double tax ) { this->consumption_tax = tax; };
    void set_amount( double amount ) { this->amount = amount; };
    void set_total( double total ) { this->total = total; };
    
    //ゲッター
    int get_count() const { return this->count; };
    double get_consumption_tax() const { return this->consumption_tax; };
    double get_amount() const { return this->amount; };
    double get_total() const { return this->total; };
};


/*----------売上----------*/
struct Sales {
private:
    string name; //名前
    int price; //単価
    SalesDetails*  datas; //売上明細データ
    int count; //明細データの数
public:
    //コンストラクタ
    Sales( ) : name(), price( 0 ), datas(), count( 0 ) {};
    Sales( const string name, int price ) 
        : name( name ), price( price ), datas(), count( 0 )  {};
    
    //セッター
    void set_price( int price ) { this->price = price; };
    void set_name ( string name ) { this->name = name; };
    void set_details( SalesDetails datas[] ) { this->datas = datas; };
    void set_detailCount( int count ) { this->count = count; };
    
    //ゲッター
    int get_price() const { return this->price; };
    string get_name() const { return this->name; };

    /*
        売上明細の各種計算項目を算出します
    */
    void Calculate( SalesDetails* datas, int count ) 
    {
        for ( int i = 0; i < count; i++ ) {
            datas[ i ].set_amount( this->price * datas[ i ].get_count() );
            datas[ i ].set_consumption_tax( datas[ i ].get_amount() * 0.05 );
            datas[ i ].set_total( datas[ i ].get_amount() + datas[ i ].get_consumption_tax() );
        }
        this->datas = datas;
        this->count = count;
    }

    /*    
        並列処理用オペレーター
        売上明細の各種計算項目を算出します
    */
    void operator() ( const blocked_range<size_t>& range ) const 
    {
        SalesDetails* tmp = this->datas;
        for ( size_t i = range.begin(); i != range.end(); i++ ) {
            tmp[ i ].set_amount( this->price * tmp[ i ].get_count() );
            tmp[ i ].set_consumption_tax( tmp[ i ].get_amount() * 0.05 );
            tmp[ i ].set_total( tmp[ i ].get_amount() + tmp[ i ].get_consumption_tax() );
        }
    };
};


/*----------売上情報を解析するオブジェクト----------*/
class Analyzer {
public:
    /* 
        売上明細で算出するべき項目を【直列で】計算します。
    */
    void Calculate( Sales* target, SalesDetails* datas, int count ) 
    {
        target->Calculate( datas, count );
    };

    /* 
        売上明細で算出するべき項目を【並列で】計算します。
    */
    void ParallelCalculate( Sales* target, SalesDetails* datas, int count ) 
    {
        Sales obj( target->get_name(), target->get_price() );
        obj.set_details( datas );
        obj.set_detailCount( count );
        parallel_for( blocked_range<size_t>( 0, count, 10000 ), obj );
    };
};


int _tmain(void)
{
    //ロケールを設定してコンソールで日本語表示ができるようにする
    _tsetlocale( LC_ALL, _T("") ); 

    //ランダムに数を生成するための準備
    srand( static_cast<unsigned int> ( time( NULL ) ) );

    //売上分析の対象となる商品を設定
    Sales* target = new Sales( "yakitori", 100 );
    string name = target->get_name();
    cout << "これから";
    copy( name.begin(), name.end(), ostream_iterator<char>( cout ) );
    cout << "(" << target->get_price() << "円)の売上を分析します・・・" << endl;

    //売上明細情報を生成
    const int count = 1000000;
    SalesDetails* datas = new SalesDetails[ count ];
    for ( int i = 0; i < count; i++ ) {
        SalesDetails data;
        int number = static_cast<int>( ( rand() % 100 ) + 1 );
        data.set_count( number );
        datas[ i ] = data; 
    }

    //各種変数を初期化
    task_scheduler_init init;
    Analyzer* analyzer = new Analyzer();

    //直列で解析
    tick_count start = tick_count::now();
    analyzer->Calculate( target, datas, count );
    tick_count end = tick_count::now();
    double second = ( end - start ).seconds();
    cout << "直列で計算した場合" <<  second << "秒かかりました。" << endl;

    //後にエラーチェックするために直列処理の計算結果を退避&初期化
    SalesDetails* olds = new SalesDetails[ count ];
    for ( int i = 0; i < count; i++ ) {
        olds[ i ] = datas[ i ];
        datas[ i ].set_amount( 0 );
        datas[ i ].set_consumption_tax( 0 );
        datas[ i ].set_total( 0 );
    }

    //並列で解析
    start = tick_count::now();
    analyzer->ParallelCalculate( target, datas, count );
    end = tick_count::now();
    double paralleSecond = ( end - start ).seconds();
    cout << "並列で計算した場合" << paralleSecond << "秒かかりました。" << endl;

    //エラーチェック
    int error = 0;
    for ( int i = 0; i < count; i++ ) {
        if ( datas[ i ].get_count() != olds[ i ].get_count() ) error = 1;
        if ( datas[ i ].get_amount() != olds[ i ].get_amount() ) error = 1;
        if ( datas[ i ].get_consumption_tax() != olds[ i ].get_consumption_tax() ) error = 1;
        if ( datas[ i ].get_total() != olds[ i ].get_total() ) error = 1;
        if ( error == 1 ) {
            cerr << "直列処理と並列処理の値が一致しません。";
            cout << endl;
            return 1;
        }
    }

    //終了処理
    cout << "処理効率は" << ( second / paralleSecond ) << "倍です。" << endl;
    cout << endl << endl << endl;
    delete analyzer;
    delete target;
    return 0;
}

 サンプルコード一式は記事にも添付しているので、適宜ダウンロードしてParallelForSampleプロジェクトを確認してみて下さい。

 このサンプルの内容は、トランザクションデータである売上明細内の未計算項目を算出する単純なものです。主な処理の流れは次の通りです。

  1. 売上分析の対象となる商品(Sales:売上オブジェクト)を設定します。
  2. 売上明細情報(SalesDetails:売上明細オブジェクト)を生成します。
  3. インテルTBBを使用するためにtask_scheduler_initオブジェクトを初期化します。
  4. 直列(並列化しない)で各明細の売上金額(数量*単価)と税金額(売上金額*0.05)およびその合計を算出して設定します。またその処理時間を計測します。
  5. 後でエラーチェックをするために直列で算出したデータを退避して、その後計算結果を初期化します。
  6. 今度は並列化して各明細の売上金額(数量*単価)と税金額(売上金額*0.05)およびその合計を算出して設定します。またその処理時間を計測します。
  7. 直列処理の結果と並列処理の結果を比較してエラーチェックをします。
  8. 処理効率を表示して終了します。

 次項以降で並列処理についての解説を少しずつ行います。


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

著者プロフィール

  • インドリ(インドリ)

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

バックナンバー

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