SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

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

スレッドセーフとインテルTBBのコンテナ

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


  • X ポスト
  • このエントリーをはてなブックマークに追加

スレッドセーフな動的配列:concurrent_vector

 プログラミングをしていると、動的にサイズを変えられる配列が便利な場面が多々あります。従来のC++プログラミングでは、動的配列としてvectorコンテナを使用しましたが、TBBによる並列プログラミングではconcurrent_vectorを使用します。

 concurrent_vectorは並列用に作られたものなので、vectorと少し違いますが、よく似ているのですぐに使えるようになると思います。サンプルプロジェクトVectorConcurrent_Vectorを用意したので見てください。使い方そのものは簡単なので、サンプルを見れば基本的な仕組みは分かると思います。

Vector:vectorの基本的な使い方を示すサンプル
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main(void)
{
    //char型の配列
    char ch[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
    //C形式文字列末尾を除外したサイズ
    int size = ( sizeof( ch ) / sizeof( char ) ) - 1;
   
    //char型のvectorを用意する
    vector<char> v1;
    for ( int i = 0; i < size; ++i )
        v1.push_back( ch[ i ] );

    //viの要素を表示
    cout << "初期要素:";
    copy( v1.begin(), v1.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;

    //要素を削除して表示
    v1.erase( v1.begin() + 5, v1.end() );
    cout << "F以降を削除した後の要素:";
    copy( v1.begin(), v1.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;

    //要素をコピー
    vector<char> v2( v1 );
    cout << "コピーした要素:";
    copy( v2.begin(), v2.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;

    //任意の要素を追加
    cout << "要素のサイズ:" << v2.size() << endl;
    cout << "要素XYZを追加:";
    const char* string = "XYZ";
    size_t strSize = strlen( string );
    for ( int i = 0; i < strSize; ++i )
        v2.push_back( string[ i ] );
    copy( v2.begin(), v2.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;
    cout << "要素のサイズ:" << v2.size() << endl;

    //領域を拡張
    cout << "領域を2文字ぶん拡張します・・・" << endl;
    v2.resize( v2.size() + 2, ' ' );
    cout << "要素のサイズ:" << v2.size() << endl;

    //要素を変更
    cout << "最後の2文字を@@に変更:";
    v2[ v2.size() - 2 ] = '@';
    v2[ v2.size() - 1 ] = '@';
    copy( v2.begin(), v2.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;

    //終了
    cout << endl << endl;
    return 0;
}
Concurrent_Vector:Concurrent_Vectorの基本的な使い方を示すサンプル
#include <iostream>
#include "tbb/concurrent_vector.h"
#include <algorithm>
using namespace std;
using namespace tbb;

int main(void)
{
    //char型の配列
    char ch[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
    //C形式文字列末尾を除外したサイズ
    int size = ( sizeof( ch ) / sizeof( char ) ) - 1;
   
    //char型のvectorを用意する
    concurrent_vector<char> v1;
    for ( int i = 0; i < size; ++i )
        v1.push_back( ch[ i ] );

    //viの要素を表示
    cout << "初期要素:";
    copy( v1.begin(), v1.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;

    //要素を削除して表示
    v1.clear();
    for ( int i = 0; i < 5; ++i )
        v1.push_back( ch[ i ] );
    cout << "F以降を削除した後の要素:";
    copy( v1.begin(), v1.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;
       
    //要素をコピー
    concurrent_vector<char> v2( v1 );
    cout << "コピーした要素:";
    copy( v2.begin(), v2.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;

    //任意の要素を追加
    cout << "要素のサイズ:" << v2.size() << endl;
    cout << "要素XYZを追加:";
    const char* string = "XYZ";
    size_t strSize = strlen( string );
    std::copy( string, string + strSize, v2.grow_by( strSize ) );
    copy( v2.begin(), v2.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;
    cout << "要素のサイズ:" << v2.size() << endl;

    //領域を拡張
    cout << "領域を2文字ぶん拡張します・・・" << endl;
    v2.grow_to_at_least( v2.size() + 2 );
    cout << "要素のサイズ:" << v2.size() << endl;

    //要素を変更
    cout << "最後の2文字を@@に変更:";
    v2[ v2.size() - 2 ] = '@';
    v2[ v2.size() - 1 ] = '@';
    copy( v2.begin(), v2.end(), ostream_iterator<char>( cout, " " ) );
    cout << endl;

    //終了
    cout << endl;
    return 0;
}

 サンプルを見比べると、vectorconcurrent_vectorは非常に似ていますが、違いが3点あることが分かります。この部分がconcurrent_vectorの特性を表しています。

 1つ目の違いは、concurrent_vectorの方が削除が不便である点です。vectorでは、eraseメソッドを使用して簡単に指定範囲の要素を削除できます。一方concurrent_vectorclearメソッドを使用して一度すべての要素を削除してから、残すべき要素を追加する必要があります。この理由はconcurrent_vectorが並列時のパフォーマンス向上を目指して作られているからです。並列処理時に要素の削除を認めてしまうと、内部に保持している要素を移動しなくてはなりません。これを行うには広い範囲のロックをする必要性が生じ、パフォーマンスに悪影響を及ぼします。そのため、concurrent_vectorは基本的に追加するのみと考えたらよいでしょう。なお、clearメソッドは並列処理用ではないので、絶対にこのメソッドを並列的に実行しないでください。

 2つ目の違いは、要素の追加の仕方が違うところです。並列処理ではコンテナは同時に複数のスレッドからメソッドが呼び出されます。従って、コンテナが保持する領域を安全に拡張する手段が必要となります。そのためのメソッドがgrow_byメソッドとgrow_to_at_leastメソッドです。grow_byメソッドは、指定数ぶんの連続する要素をベクトルに安全に追加して、最初に追加した要素のイテレータ(反復子)を返します。TBB2.0ではint型のインデックス値を返すようになっていましたが、今ではイテレータを返すように変更されています。十分に注意してください。

 3つ目の違いは、リサイズの方法が違う点です。先ほど少し触れましたが、並列処理時に安全に領域を拡張するためgrow_to_at_leastメソッドが用意されています。このメソッドを使用すると、複数のスレッドからコンテナがアクセスされても安全に領域を拡張できます。

 最後に並列処理時に注意するべきことを書きます。それは、concurrent_vectorの構築中は、並列的にアクセスしてはならないことです。concurrent_vectorはスレッドセーフなコンテナですが、初期化処理を正しく行う前では別です。そのため、並列処理をする前にconcurrent_vectorコンテナを正しく初期化してください。これはどのコンテナにも共通しており、以降解説するコンテナにも同じことが当てはまります。

次のページ
並列キュー:concurrent_queue

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
インテルTBBを通じて学ぶ並列処理連載記事一覧

もっと読む

この記事の著者

インドリ(インドリ)

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

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4861 2010/04/27 12:09

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング