今回は
前回予告した通りに、
parallel_pipelineの解説を行います。
先ずは、前回掲載したシーザー暗号のサンプルプログラムを、parallel_pipelineテンプレートを使って書き変えたプログラムを見て下さい。
#include <iostream>
#include <fstream>
#include "tbb/pipeline.h"
using namespace std;
using namespace tbb;
/*-------------------------------------------------------------
フィルタークラス間でやり取りをするデータを管理するクラス
---------------------------------------------------------------*/
class Buffer
{
private:
static const size_t buffer_size = 100;
char* end_ptr;
char data[ buffer_size ];
public:
char* begin() { return data; }
const char* begin() const { return data; }
char* end() const { return end_ptr; }
void set_end( char* new_ptr ) { end_ptr = new_ptr; }
size_t max_size() const { return buffer_size; }
size_t size() const { return end_ptr - begin(); }
};
/*------------------------------------------------------
指定されたファイルからデータを読み取るクラス
--------------------------------------------------------*/
class InputFileFilter
{
public:
static const size_t buffer_Count = 4;
private:
ifstream* file;
size_t next_buffer;
Buffer buffer[ buffer_Count ];
public:
InputFileFilter( ifstream* name ) :
next_buffer( 0 ), file( name ) { }
InputFileFilter( const InputFileFilter& f ) :
next_buffer( 0 ), file( f.file ) { }
Buffer* operator()( flow_control& fc )
{
//入力データをファイルから読みだす
Buffer& b = buffer[ next_buffer] ;
next_buffer = ( next_buffer + 1 ) % buffer_Count;
file->read( b.begin(), b.max_size() );
int count = file->gcount();
//終了判定が重要
if ( count == 0 ) {
fc.stop();
return NULL;
} else {
b.set_end( b.begin() + count );
return &b;
}
}
};
/*---------------------------------------------------
受け取った文字列をシーザー暗号化するクラス
-----------------------------------------------------*/
class CaesarTransformFilter
{
private:
int key;
public:
CaesarTransformFilter( int key ) :
key( key ) { }
Buffer* operator()( Buffer* item )
{
for( char* s = item->begin(); s != item->end(); ++s ) {
char val = *s;
if ( ( val + key ) > 90 ) {
*s = 64 + ( val + key - 90 );
} else {
*s = val + key;
}
}
return item;
}
};
/*---------------------------------------------------------
受け取ったバッファの内容をファイルへ書き込むクラス
-----------------------------------------------------------*/
class OutputFileFilter
{
ofstream* file;
public:
OutputFileFilter( ofstream* name ) :
file( name ) { }
OutputFileFilter( const OutputFileFilter& f ) :
file( f.file ) { }
void operator()( Buffer* item )
{
file->write( item->begin(), item->size() );
file->flush();
}
};
/*------------------------------------------------------
パイプラインパターンでシーザー暗号処理を実行する
※基本的な書き方
--------------------------------------------------------*/
void PipelineCaesar( string inputFileName, int key, string outputFileName )
{
//使用するファイルを準備
ifstream inputFile(inputFileName);
ofstream outputFile( outputFileName );
//各種フィルターを用意
filter_t< void, Buffer* > inputFilter(
filter::serial_in_order,
InputFileFilter( &inputFile ) );
filter_t< Buffer*, Buffer* > transFilter(
filter::parallel,
CaesarTransformFilter( key ) );
filter_t< Buffer*, void > outputFilter(
filter::serial_in_order,
OutputFileFilter( &outputFile ) );
filter_t< void, void > all_filter(
inputFilter & transFilter & outputFilter );
//パイプライン実行
parallel_pipeline(
InputFileFilter::buffer_Count,
all_filter );
//念のためにファイルを閉じる
inputFile.close();
outputFile.close();
};
/*------------------------------------------------------
パイプラインパターンでシーザー暗号処理を実行する
※make_filterを使用した場合
--------------------------------------------------------*/
void PipelineCaesar1( string inputFileName, int key, string outputFileName )
{
//使用するファイルを準備
ifstream inputFile(inputFileName);
ofstream outputFile( outputFileName );
//パイプライン実行
parallel_pipeline(
InputFileFilter::buffer_Count,
make_filter< void, Buffer* > (
filter::serial_in_order,
InputFileFilter( &inputFile ) ) &
make_filter< Buffer*, Buffer* > (
filter::parallel,
CaesarTransformFilter( key ) ) &
make_filter< Buffer*, void > (
filter::serial_in_order,
OutputFileFilter( &outputFile ) )
);
//念のためにファイルを閉じる
inputFile.close();
outputFile.close();
};
/*------------------------------------------------------
サンプルデータをシーザー暗号化する処理を行う
--------------------------------------------------------*/
int main()
{
//サンプルファイルを作成
string inputFileName = "TestData.txt";
ofstream file( inputFileName );
for ( char i = 65; i < 91; i++ ) {
file << i;
}
file.close();
//確認
ifstream fout( inputFileName );
char c;
cout << "変換前の文字" << endl;
while( !fout.eof() ) {
fout >> c;
if ( !fout.eof() ) cout << c << ' ';
}
cout << endl;
fout.close();
//パイプライン実行
int key = 1;
string outputFileName = "CaesarData.txt";
PipelineCaesar( inputFileName, key, outputFileName );
//確認
cout << endl;
ifstream fout1( outputFileName );
cout << "変換後の文字" << endl;
while( !fout1.eof() ) {
fout1 >> c;
if ( !fout1.eof() ) cout << c << ' ';
}
cout << endl << endl;
fout1.close();
}
処理内容は前回と同じですが、
関数呼び出し演算子が強く型付けされている点が違います。前回のサンプルは、型付けが不十分で、voidポインターを使用していましたが、今回はBuffer型を明示的に指定しています。これにより、つまらないエラーを減らす事が出来ます。その他にも差異がいくつかありますので、これから1つずつ解説していきます。
関数呼び出し演算子以外にも、フィルタークラスの定義方法が違います。pipelineテンプレートを使用した前回のサンプルは、tbb::filterクラスから派生させて、親のコンストラクタにモードを指定する必要がありました。ですが、parallel_pipelineを使用する場合、使用するフィルタークラスは
tbb::filterクラスから派生させる必要がありません。
parallel_pipelineテンプレートを使用する場合は、filter_tテンプレートを使用します。
parallel_pipelineテンプレートで使用する各フィルタークラスは、
filter_tテンプレートのインスタンスを通して、間接的に指定します。filter_tテンプレートのインスタンスの生成法は2つあり、それがプログラムの違いを生みます。
1つめは、
コンストラクタを使って、filter_tテンプレートのインスタンスを生成する方法です。サンプルプログラムのPipelineCaesarがこの方法でプログラミングしたものです。この方法は見やすいという利点がありますが、いちいちフィルターオブジェクトに命名しなくてはならず、プログラムが冗長になってしまいます。これは個人的な感想ですが、動的にフィルタークラスを追加する必要がなければ、この方法はお勧めできません。
2つめは、
make_filter関数を使用して、filter_tテンプレートのインスタンスを生成する方法です。サンプルプログラムのPipelineCaesar1がこの方法でプログラミングしたものです。この方法を使うと、プログラムが簡潔になります。また、ラムダ式とも親和性が高い方法で、フィルターが固定化している場合はこちらをお勧めします。
ラムダ式を使うと、プログラムが簡潔なものとなります。気になる方も多いと思いますので、ラムダ式を使用した場合のシーザー暗号プログラムを掲載します。
#include <iostream>
#include <fstream>
#include "tbb/pipeline.h"
using namespace std;
using namespace tbb;
/*-------------------------------------------------------------
フィルタークラス間でやり取りをするデータを管理するクラス
---------------------------------------------------------------*/
class Buffer
{
private:
static const size_t buffer_size = 100;
char* end_ptr;
char data[ buffer_size ];
public:
char* begin() { return data; }
const char* begin() const { return data; }
char* end() const { return end_ptr; }
void set_end( char* new_ptr ) { end_ptr = new_ptr; }
size_t max_size() const { return buffer_size; }
size_t size() const { return end_ptr - begin(); }
};
/*------------------------------------------------------
パイプラインパターンでシーザー暗号処理を実行する
※ラムダ式を使用した場合
--------------------------------------------------------*/
void PipelineCaesar( string inputFileName, int key, string outputFileName )
{
//使用するファイルを準備
ifstream inputFile( inputFileName );
ofstream outputFile( outputFileName );
//使用する変数を用意
int buffer_Count = 4;
size_t next_buffer = 0;
Buffer* buffer = new Buffer[ buffer_Count ];
//パイプライン実行
Buffer* b;
parallel_pipeline(
buffer_Count,
make_filter< void, Buffer* > (
filter::serial_in_order,
//入力データをファイルから読みだす
[ &buffer, &next_buffer, &buffer_Count, &inputFile]
( flow_control& fc ) -> Buffer*
{
Buffer& b = buffer[ next_buffer] ;
next_buffer = ( next_buffer + 1 ) % buffer_Count;
inputFile.read( b.begin(), b.max_size() );
int count = inputFile.gcount();
if ( count == 0 ) {
fc.stop();
return NULL;
} else {
b.set_end( b.begin() + count );
return &b;
}
}
) &
make_filter< Buffer*, Buffer* > (
filter::parallel,
//入力ストリームに対してシーザー暗号化を行う
[ &key ] ( Buffer* item ) -> Buffer*
{
for( char* s = item->begin(); s != item->end(); ++s ) {
char val = *s;
if ( ( val + key ) > 90 ) {
*s = 64 + ( val + key - 90 );
} else {
*s = val + key;
}
}
return item;
}
) &
make_filter< Buffer*, void > (
filter::serial_in_order,
//暗号化したデータをファイルへ出力
[ &outputFile ] ( Buffer* item )
{
outputFile.write( item->begin(), item->size() );
outputFile.flush();
}
)
);
//念のためにファイルを閉じる
inputFile.close();
outputFile.close();
};
/*------------------------------------------------------
サンプルデータをシーザー暗号化する処理を行う
--------------------------------------------------------*/
int main()
{
//サンプルファイルを作成
string inputFileName = "TestData.txt";
ofstream file( inputFileName );
for ( char i = 65; i < 91; i++ ) {
file << i;
}
file.close();
//確認
ifstream fout( inputFileName );
char c;
cout << "変換前の文字" << endl;
while( !fout.eof() ) {
fout >> c;
if ( !fout.eof() ) cout << c << ' ';
}
cout << endl;
fout.close();
//パイプライン実行
int key = 1;
string outputFileName = "CaesarData.txt";
PipelineCaesar( inputFileName, key, outputFileName );
//確認
cout << endl;
ifstream fout1( outputFileName );
cout << "変換後の文字" << endl;
while( !fout1.eof() ) {
fout1 >> c;
if ( !fout1.eof() ) cout << c << ' ';
}
cout << endl << endl;
fout1.close();
}
見ての通りで、ラムダ式を使うとプログラムが短くなります。ただし、ラムダ式は使えばいいというものではありません。よく考えて、適切な状況で使用しましょう。
【参考資料】
テーマ : プログラミング
ジャンル : コンピュータ