fc2ブログ

アジャイル開発と契約0

 私はシステム屋ですので、アジャイル開発を実践してきました。その中で一番難しいと感じたのが、契約に関する事柄です。アジャイル開発を導入する上で一番の難問が契約です。契約が成立しなければ、そもそも開発が始められません。しかしながら、実務面に於いて、最重要課題と言える契約について書かれた技術文章が見当たらないので、自身の体験をもとに書く事にしました。
 契約面で一番最初の難関が、日本は契約を極力避ける事です。日本は、良くも悪くも契約を重視するビジネススタイルを好まず、お客様をお殿様扱いする上下関係のあるビジネススタイルを基本とします。コストを下げる働きもありますので、これが一概に悪いとは言えません。しかしながらこのスタイルは、トラブルとバグを生む上に、アジャイルなシステム開発に向きません。
 アジャイル開発は比較的短い期間で、設計・実装・テストといったライフサイクルを繰り返します。そうする事により、柔軟かつ迅速に物事に対応できます。ここで問題となるのが契約についてです。ウォーターフォールモデルとは違い、予め全てを決めませんので、契約内容が問題となります。これは、実際に体験をした人だけ分かる事柄であり、経験がない人には分かり難いと思いますので、例を挙げて説明します。
 例えば、日本では多くの場合、期間を見積もって人月単価ベースで契約を結びます。しかしながら、お客様の要望ははっきりしておらず、開発側もお客様の業務についての知識が足りないので、その見積もりは大概外れています。ウォーターフォールモデルでは、最終段階でお客様が要望と製品のズレを感じる事になりますので、後で契約を微調整する事が頻繁に行われています。当然この方法は、アジャイル開発で通用しません。何故ならば、お客様とのコミュニケーションを頻繁に行い、成果も目に見えますので、ズレが早く判明してしまうからです。そうなると、先行見積もり形式の契約では、双方が納得かないのは目に見えています。お客様側からしてみれば、先に見積もった金額の論理的整合性に疑問を持つ事は当然です。一方開発側にしてみれば、往々にして追加注文となりがちなお客様の要望を実現するにも関わらず、当初それを想定していない期間と金額を見積もっているのですから割りにありません。
 従って、アジャイル開発は一定の期間ごとに個別に契約を結ぶ方式が最適だと言えます。しかながら、単純に期間ごとに契約を結べば、その交渉コストは無視できないものとなります。ですから、ベースとなる基本契約を結び、その上で別途個別契約を結ぶのがベストだと私は考えております。今回はこれで終わりです。次回から具体的な内容を書いていきます。
スポンサーサイト



テーマ : ソフトウェア開発
ジャンル : コンピュータ

アジャイル開発が日本で普及しない理由

 アジャイル開発が提唱されてずいぶん経ちます。しかし、日本ではあまり普及していません。その理由は様々ですが、私は日本の情報産業の体質と、日本人特有の商習慣が一番大きな要因だと考えております。これからその理由を書きます。
 話しを円滑に進めるために、先ずはアジャイル開発で良くある誤解について触れます。アジャイル開発を知らない人は、プロトタイプモデルと同じと考える傾向があります。しかし、アジャイル開発はプロトタイプモデルではありません。「設計書などの書類を作成せずに実装を行う開発手法」といったイメージを持つ人が多いのですが、アジャイル開発はプロトタイプモデルと本質的に異なります。
 アジャイル開発の本質は、物事に柔軟かつ迅速に対処する事です。それは、プログラミングだけに限った話ではありません。テストファーストのイメージが強い人が多々見受けられますが、アジャイル開発は、実装段階を工夫するだけの開発手法ではありません。全行程とそれにかかわる全ての人々が、アジャイル開発の守備範囲です。テストファーストやペアプログラミングといった、プログラミングに関する考え方が目立ちますが、設計段階はもちろんのこと、それより上の工程もアジャイル的思考をもって取り組まないと、アジャイル開発は成功しません。
 業務で何度かアジャイル開発の失敗例を見聞きしましたが、一番多い原因は、プログラマ以外はアジャイルに取り組まなかった事です。ペアプログラミングやテストファースを場当たり的に実施しても、関係者全員がアジャイルに取り組まないと高い確率で失敗すると言っても過言ではありません。
 日本の情報産業は多重請負体質ですので、アジャイル開発を本格的に実施するとなるとそれが障害になり、柔軟かつ迅速に行動できません。一々お上の判断を仰いでいては、硬直的で遅くなるのは当然です。例えば、設計のバグを見つけたとします。その件について、エンドユーザーの確認を取りたい場合、多重請負体制では上の会社を通さねばなりません。何故ならば、エンドユーザーと交渉するのは、元請けだと決まっているケースが大半だからです。その様な状態ですと、迅速に行動出来る筈がありません。また、上下関係と役割が厳格に決まっていますので、柔軟に行動する事も出来ません。これでは、アジャイル開発を適用したプロジェクトが失敗して当然です。この状況の中できるのは、ウォーターフォールモデルの一部でアジャイル開発の手法を一部取り入れるぐらいのものです。本格的なアジャイル開発をした日本の会社は恐らく稀でしょう。
 日本の情報産業の体質は大問題ですが、アジャイル開発が成功しない理由はこれだけではありません。日本人特有のお客様は神様だという考え方も問題です。システム開発は、お客様も協力せねば成功しません。特に、コミュニケーションを重要視するアジャイル開発では、その傾向が強くなります。しかし、日本では「お金を払ってやるんだから良きに計らえ」といった考えのお客様が多くいます。情報処理技術は魔法ではなく、技術者達はエスパーではありません。作ってほしいものを期待して待っているだけでは、システムは出来上がりません。お客様サイドも積極的に開発に参加しなければならないのです。ですが、日本は「良きに計らう」が商習慣としてありますので、非常に根が深い問題です。
 以上で分かると思いますが、現在の日本はアジャイルに開発できない風土なのです。アジャイル開発が成功しないのは、小手先の問題ではなく、非常に根が深い問題があるからです。ですが、日本が何時までもウォーターフォールモデルで、遅く硬直した状態で開発していれば、他国との差が開く一方なのは目に見えています。アジャイルな思考を普及させるのは急務だと言えるでしょう。

テーマ : ソフトウェア開発
ジャンル : コンピュータ

本当に不変?C言語のconst型修飾子

 変数の値を定数にしたい時、C言語ではconst型修飾子を使います。

#include <stdio.h>
int main( void )
{
    const int value = 10;
    value = 10; //コンパイルエラーが出る
    return 0;
}

 こうすると、「 error C2166: 左辺値は const オブジェクトに指定されています。」の様に、コンパイルエラーが出て、変数を変更することを防げます。この例をみると、const型修飾子は簡単なものだと感じるでしょう。
 しかし、const型修飾子はポインタが絡むと複雑になります。const型修飾子付きポインタ変数を使ったサンプルを見て下さい。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int value1 = 20;
    const int* ptr = &value;
    printf( "初期値%d\n", *ptr );
    //*ptr = 20; //コメントを外すとエラーが発生
    ptr = &value1; //変更してもエラーが出ない
    printf( "現在の値%d\n", *ptr );
    return 0;
}

 期待通り*ptr = 20;の様なコードで変数を直接変更する事は出来ません。しかし、ポインタの値を変更する事により値を変更できます。ポインタの値を変更する事を防ぐには、const型修飾子を使って、変数を定数ポインタとして宣言します。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int value1 = 20;
    int * const ptr = &value;
    printf( "初期値%d\n", *ptr );
    *ptr = 20; //今度は直接値を変更出来てしまう
    //ptr = &value1; //コメントを外すとエラーが発生
    printf( "現在の値%d\n", *ptr );
    return 0;
}

 今度は逆に、ポインタの値は変更できないものの、変数の値は直接変更出来てしまいます。ポインタおよび変数の値の両方を不変にしたければ、const型修飾子を2回指定します。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int value1 = 20;
    const int * const ptr = &value;
    printf( "初期値%d\n", *ptr );
    //*ptr = 20; //コメントを外すとエラーが発生
    //ptr = &value1; //コメントを外すとエラーが発生
    printf( "現在の値%d\n", *ptr );
    return 0;
}

 こうすれば変数を不変にできると、考える人もいるでしょう。しかし、これでもまだ値を変更する方法が存在します。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int * const x = &value;
    const int * const ptr = &value;
    printf( "初期値%d\n", *ptr );

    //他の変数を使用して値を変更
    *x = 20;
    printf( "現在の値%d\n", *ptr );

    //直接値を変更
    value = 100;
    printf( "現在の値%d\n", *ptr );

    return 0;
}

 このサンプルが示すように、const型修飾子は間接的に値を変更する事を防げません。また、参照元の変数を直接変更できる事も忘れてはなりません
 const型修飾子は定数を指定したい時に使用されますが、実際は定数ではありません。constを指定した変数は代入出来ない事を意味しているだけなのです。const型修飾子を指定しても変数は変更される恐れがあります。従って、チーム開発でプログラミングをする場合、コーディングルールが重要だと言えます。
 const型修飾子を使用する際には、十分に注意しましょう。

テーマ : プログラミング
ジャンル : コンピュータ

C言語のif文

 今回はC言語のif文を取り上げます。if文は単純な文ですが、落とし穴が存在します。落とし穴について記述する前に、if文についておさらいします。

#include <stdio.h>
int main()
{
    int i;
    i = 0;
    if ( i ) printf( "0 = true\n" );
    else printf( "0 = false\n" );
    i = 1;
    if ( i ) printf( "1 = true\n" );
    else printf( "1 = false\n" );
    i = -1;
    if ( i ) printf( "-1 = true\n" );
    else printf( "-1 = false\n" );

    return 0;
}

 C言語のif文は括弧内の式の値が0以外ならばthen節の文が実行されます。そして、括弧内の式の値が0で、else節があれば、else節の文が実行されます。
 従って、以下のプログラムは、else節がないので何の文も実行しません。

    i = 0;
    if ( i ) printf( "ここは表示されない\n" );

 では、次のサンプル「何が表示される?」を実行すると、何が表示されるでしょうか?実行する前に考えて下さい。

//何が表示される?
#include <stdio.h>
int main()
{
    int i;
    i = 0;
    if ( i != 0 )
        if ( i >= 0 ) printf( "iは0以上。\n" );
    else printf( "iは0\n" );

    return 0;
}

 答えは・・・何も表示されません。「iは0」と表示されると考えた人が多いと思いますが、elseは最も内側のif文に対応するので、式「if >= 0」の判定結果がelaseの場合に「iは0」と表示されます。このプログラムは意図通り動作しないのです。
 変数が0の時にメッセージを表示したい場合、サンプル「分かりやすい表示」で示す様に{}を付けます。

//分かりやすい表示
#include <stdio.h>
int main()
{
    int i;
    i = 0;
    if ( i != 0 ) {
        if ( i >= 0 ) printf( "iは0以上。\n" );
    } else printf( "iは0\n" );

    return 0;
}

 こうすれば「iは0」と表示されます。この教訓は、if文の入れ子がある場合、複合文を使用するべきです。{}をつけて、複合文にすれば誤読やミスを防げます。
 他にもif文の落とし穴は存在します。次のサンプル「落とし穴」もよくある間違いです。

//落とし穴
#include <stdio.h>
int main()
{
    int i;
    i = 1;
    printf( "変数i = %d\n", i );
    if ( i = 0 ) printf( "iは0\n" );
    else printf( "iは0ではない\n" );
    printf( "変数i = %d\n",  i);

    return 0;
}

 サンプル「落とし穴」は、if文の式でi == 0 とするべき所を、i = 0としているので、変数の値が変えられています。比較するつもりで変数の値を変えてしまえば、以前の値に関係なく何らかの値が代入されるので、予期せぬバグが発生します。
 これを避ける方法は、2つあります。1つめは、コンパイラオプションを指定して、警告をエラーとして扱う方法です。2つめは、比較値を左( if ( 0 = i ) printf( "iは0\n" );)にして、=を使うとコンパイルエラーを発生させる方法です。
 if文の様に、単純な構文でもバグは潜んでいます。十分に注意しましょう。

テーマ : プログラミング
ジャンル : コンピュータ

インクルードファイルの意味を考える

 初めてC言語でのプログラミングを学習した時、Hello worldプログラムを打ったという人が多いと思います。この方法は凄くよいと思うのですが、#include<stdio.h>が具体的に何をするのか、非常に気になりました。同じ事を感じた人も多いと思います。そこで今回はその件について書きます。
 先ずは、Hello worldプログラムに、#include<stdio.h>が絶対必要なのかと考え調べた結果、必ずしも必要ではありませんでした。
 次のプログラムでも実行出来ます。

int printf( const char * _Format, ...); 
int main() { return printf( "Hello world!\n" ); }

 わずか二行です。コンソール画面に「Hello world」と表示するぐらいならば、#includeは必要ありません。このプログラムはprintf関数の定義が必要なだけなので、printf関数を定義すればコンパイルできます。
※コンパイラはこの宣言文を使って、引数をチェックします。その為に、関数の引数の定義が必要となります。
 では、#includeとは何をするものなのでしょうか?それを知るには、#includeの意味をおさらいする必要があります。
 名著「S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル 」によると#includeは、指定されたソーステキストファイルの内容が、#include指令行の場所に現れたかのように処理するプリプロセッサです。この定義は分かり難いので、実際にプリプロセッサ適用後、ソースの内容がどのように変化するのかを見てみましょう。
 プリプロセッサ適用後の内容を見る方法は、コンパイラにより異なりますが、大概のコンパイラはコンパイルオプションを指定すれば、見る事が出来ます。例えば、VC++ならばPオプションを使用します。そして、GCCならば-save-tempsオプションを指定し、中間ファイルの保存を指示する事により、拡張子iのインクルードファイルを見る事が出来ます。
 このファイルを見ると、#includeが何をしているのか分かると思います。#includeプリプロセッサ指令文は、コンパイラがプログラムをチェックするために、関数の定義を展開しています。
 なお、#include<stdio.h>は必ずしも、stdio.hファイルを指しているとは限らない事に注意して下さい。C言語の規格では、#include<>が本当のファイル名を指している事を要請していません。処理系によっては違うかもしれません。

テーマ : プログラミング
ジャンル : コンピュータ

中の人の徒然草372

それにしても今年の夏は凄く暑いですね。皆さまはいかがお過ごしでしょうか。
心も体も熱いインドリです♪
数日OpenMPとインテルTBBについて書いていて感じたのですが、のびのびと書けて気持ちいいですね♪
制約がなく、自分の好きなように書けると、非常に気持ちがいいです。
書きたい事も山ほどあるし、近いうちに記事を大量UPするかもしれません。
私の書きたい事は情報処理技術全般ですから、全然書き足りません。
もっと書かねば!

テーマ : 裏事情
ジャンル :

TBBと並列ソート

 並列処理を学習していると、初期の段階で気になるのがソートです。整列処理を並列化する事は誰しも考えます。しかし、実際に自分で実装するとなると結構難しい課題です。
 幸いインテルTBBには、並列ソートを行うテンプレート「parallel_sort」が予め用意されています。parallel_sortの使用法は簡単で、std::sortとほぼ同様なので、サンプルプログラムを見れば直ぐに分かると思います。

#include <iostream>
#include <iterator>
#include <algorithm>
#include "tbb/task_scheduler_init.h"
#include "tbb/parallel_sort.h"
#include "tbb/concurrent_vector.h"
using namespace std;
using namespace tbb;

int main()
{
    //データを準備
    concurrent_vector< char > v;
    v.push_back( 'c' );
    v.push_back( 'b' );
    v.push_back( 'd' );
    v.push_back( 'a' );

    //初期データを表示
    cout << "整列前のデータ: ";
    copy( v.begin(), v.end(), ostream_iterator< char >( cout, " " ) );
    cout << endl;

    //昇順でソート
    parallel_sort( v.begin(), v.end() );
    cout << "昇順で整列後のデータ: ";
    copy( v.begin(), v.end(), ostream_iterator< char >( cout, " " ) );
    cout << endl;

    //降順でソート
    parallel_sort( v.begin(), v.end(), std::greater< char >() );
    cout << "降順で整列後のデータ: ";
    copy( v.begin(), v.end(), ostream_iterator< char >( cout, " " ) );
    cout << endl;

    cout << endl;
    return 0;
}

 parallel_sortテンプレートは、平均時間計算量0(n log n)の不安定なソートを行います。不安定というのは、同値の要素については相対的な順序を維持しないという事を意味します。同値があり得る場合は注意しましょう。並列処理では、基本的に順序が保障されないと考えるとよいでしょう。順序を保証するとパフォーマンスが下がりますので、基本的に並列処理では順序に依存しないアルゴリズムを考えます。
 parallel_sortテンプレートは、RandomAccessIteratorを使用します。STLに存在する他のイテレーターは、本質的に直列であり、並列に向いていないので使用されません。
 これは余談ですが、書籍・ インテル スレッディング・ビルディング・ブロック ―マルチコア時代のC++並列プログラミング によると、インテルTBBの初期段階の設計では、並列範囲を表現する為にRandomAccessIteratorを使用していたそうです。今では再帰的な範囲を使用しています。並列処理の範囲の表現に再帰が選ばれたのは、非常に興味深い事柄です。


【参考資料】

テーマ : プログラミング
ジャンル : コンピュータ

強く型付けするパイプラインテンプレートparallel_pipeline

 今回は前回予告した通りに、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();
}

 見ての通りで、ラムダ式を使うとプログラムが短くなります。ただし、ラムダ式は使えばいいというものではありません。よく考えて、適切な状況で使用しましょう。


【参考資料】

テーマ : プログラミング
ジャンル : コンピュータ

インテルTBBとパイプライン

 並行プログラミングモデルには、パイプラインモデルもしくはパイプラインパターンがあります。長いので、これ以降はパイプラインと記述します。
 簡潔に言いますと、パイプラインとは、ストリームをいくつかの工程で順番に処理する並行処理モデルです。
 例としてシーザー暗号を挙げます。シーザー暗号は、文字を任意の数シフト事により暗号化する暗号法です。文字「A」を1シフトすると文字「B」、文字「B」を1シフトすると文字「C」というふうに文字を暗号化します。
 サンプルプログラムを掲載しますので、試してみて下さい。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

int main(void) 
{
    //暗号対象を用意
    vector< char > strs;
    for (int i = 0; i < 26; i++ ) {
        strs.push_back( static_cast< char >( i + 65 ) );
    }

    //確認
    cout << "暗号化する前の文字" << endl;
    copy( strs.begin(), strs.end(), 
        ostream_iterator< char >( cout, " " ) );
    cout << endl << endl;

    //暗号化
    int key = 1;
    vector< char > outs;
    for ( int i = 0; i < 26; i++ ) {
        char val = strs[ i ]; 
        if ( ( val + key ) > 90 ) {
            outs.push_back( 64 + ( val + key - 90 ) ); 
        } else {
            outs.push_back( val + key );
        }
    }

    //確認
    cout << "暗号化後の文字" << endl;
    copy( outs.begin(), outs.end(), 
        ostream_iterator< char >( cout, " " ) );
    cout << endl << endl << endl;
    return 0;
}
※このサンプルはASCII文字コードを前提としています。


 この一連のシーザー暗号化処理を並列化するのは、一般的には困難です。全ての処理を並列処理化してしまうと、ファイルへ書き込む値が順不同になってしまい、データが壊れてしまうでしょうし、他にも色々な不都合が起こるでしょう。
 ですが、文字列を変換するという工程だけは、なんの処理にも依存していませんので並列化できます。ここでパイプラインの出番です。パイプラインで処理をすると、一部の処理を並列化出来ます。パイプラインを自分で実装するのは大変ですが、幸いインテルTBBには、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 tbb::filter
{
public:
    static const size_t buffer_Count = 4;
private:
    ifstream file;
    size_t next_buffer;
    Buffer buffer[ buffer_Count ];
public:
    InputFileFilter( string name ) :
      filter( serial_in_order ),
            next_buffer( 0 ),
            file( name ) 
      { cout << "@InputFileFilterのコンストラクタが呼び出されました" << endl; }

     ~InputFileFilter() { file.close(); }

     void* operator()( void* )
    {
        cout << "@InputFileFilterの関数呼び出し演算子が呼び出されました" << endl;

        //入力データをファイルから読みだす
        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 ) {
            return NULL; 
        } else {
            b.set_end( b.begin() + count );
            return &b;
        }
    }
};

/*---------------------------------------------------
    受け取った文字列をシーザー暗号化するクラス
-----------------------------------------------------*/
class CaesarTransformFilter : public tbb::filter
{
private:
    int key;
public:
    CaesarTransformFilter( int key ) : 
      tbb::filter( parallel ), key( key ) 
    {
        cout << 
            "*CaesarTransformFilterのコンストラクタが呼び出されました" << endl;
    }
      
    void* operator()( void* item )
    {
        cout << 
           "*CaesarTransformFilterの関数呼び出し演算子が呼び出されました" << endl;
        Buffer& b = *static_cast< Buffer* >( item );
        for( char* s = b.begin(); s != b.end(); ++s ) {
            char val = *s;
            if ( ( val + key ) > 90 ) {
                *s = 64 + ( val + key - 90 );
            } else {
                *s = val + key;
            }
        }
        return &b;
    }
};

/*---------------------------------------------------------
    受け取ったバッファの内容をファイルへ書き込むクラス
-----------------------------------------------------------*/
class OutputFileFilter : public tbb::filter
{
    ofstream file;
public:
    OutputFileFilter( string name  )
        : tbb::filter( serial_in_order ), file( name )
    {
        cout << 
            "+OutputFileFilterのコンストラクタが呼び出されました" << endl;
    }

     ~OutputFileFilter() { file.close(); }

     void* operator()( void* item)
    {
        cout << 
            "+OutputFileFilterの関数呼び出し演算子が呼び出されました" << endl;
        Buffer& b = *static_cast<Buffer*>( item );
        file.write( b.begin(), b.size() );
        file.flush();
        return NULL;
    }
};

/*------------------------------------------------------
    サンプルデータをシーザー暗号化する処理を行う
--------------------------------------------------------*/
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;
    cout << endl;
    fout.close();
    cout << "これからパイプラインでシーザー暗号処理を行います・・・" << endl;

    //入力フィルタを用意
    pipeline pipeline;
    InputFileFilter inputFilter( inputFileName );
    pipeline.add_filter( inputFilter );

    //変換フィルタを用意
    int key = 1;
    CaesarTransformFilter transform( key );
    pipeline.add_filter( transform );

    //出力フィルタを用意
    string outputFileName = "CaesarData.txt";
    OutputFileFilter outputFilter( outputFileName );
    pipeline.add_filter( outputFilter );

    //パイプラインで実行
    pipeline.run( InputFileFilter::buffer_Count );
    pipeline.clear();

    //確認
    cout << endl;
    ifstream fout1( outputFileName );
    cout << "変換後の文字" << endl;
    while( !fout1.eof() )
    {
        fout1 >> c;
        if ( !fout1.eof() ) cout << c << ' '; 
    }
    cout << endl << endl;
    fout1.close();
}


 このサンプルは、パイプラインを使用した簡単なサンプルです。各フィルターの動きをみる為に、coutに文字列を出力していますが、実務では止めておいた方がよいでしょう。並列的にcoutする仕様は、多くの検討が必要となる上に、利益が殆どありません。
 パイプラインを実装した事のない人には、非常に冗長で難しく思えるコードですが、自分で実装する事を考えると非常にお手軽に実装できます。それに、見けかほど難しくありません。お約束に従って実装するだけです。
 pipelineテンプレートを使う為の準備として、ストリーム入力用フィルター、ストリーム変換用フィルター、ストリーム出力用フィルターの3個のクラスを用意します。各filterクラスは、filterクラスから継承して、親クラスのコンストラクタにモード(serial_in_order、parallelなど)を指定して呼び出します。ひとまず、入力/出力用フィルターはserial_in_order、変換用フィルターはparallelを使用するとよいでしょう。
※処理内容によっては、今回の様に、各ステージでやり取りする為のクラスが別に必要となります。
 フィルタークラスの定義が少し面倒ですが、パイプラインの使用法は簡単です。filterクラスを追加後、runメソッドとclearメソッドを順番に実行するだけです。それだけで、並列的に処理されるようになります。今まで自分でパイプラインを実装してきた事を考えれば、これは大きな進歩です。
 それに、パイプラインには柔軟性があります。例えば、今回のサンプルで、排他的論理和を使用した暗号化クラスを追加し、シーザー暗号化後のデータをさらに暗号化するなどの事が可能となります。他にも、このサンプルでは意味はありませんが、いくつも変換フィルターを追加して、ストリームを何重に変換することもできます。この様に、パイプライン処理をする事により、モジュール化が進められ、柔軟な処理が可能となります。
 察しがいい方は、多くの処理がパイプラインで処理できる事に気付き、片っぱしから既存のコードを並列化したくなったかもしれません。しかし、パイプラインも万能ではありません。パイプラインは、直列部分も存在するため、パフォーマンス向上に限界があります。また、変換ロジックが大量に存在する場合に、個々のスレッドを割り当てると逆にパフォーマンスが劣化します。それで、pipelineのrunメソッドに限界値を指定する事になっています。パイプラインに使用するリソースの限界値を割り出すのは困難ですので、設計にも負担がかかります。パイプラインを使用する際には、よく検討して下さい。
 最後に注意するべき事柄を書きます。pipelineテンプレートがサポートするのは、線形パイプラインです。バロック形式のパイプラインを直接処理できません。線形順へと変換する必要があります。あと、インテルTBB3.0には強く型付けできるparallel_pipelineが存在します。parallel_pipelineについてはまた今度解説します。


【参考資料】

テーマ : プログラミング
ジャンル : コンピュータ

parallel_for_eachテンプレート

 C++標準ライブラリに、コンテナ要素に任意の単項関数オブジェクトを適用するfor_eachテンプレートがあります。インテルTBBには、これと同じ働きをするparallel_for_eachテンプレートが用意されています。
 サンプルを見れば使い方はすぐ分かると思います。

#include <iostream>
#include <functional>
#include <algorithm>
#include "tbb/task_scheduler_init.h"
#include "tbb/concurrent_vector.h"
#include "tbb/parallel_for_each.h"
using namespace std;
using namespace tbb;

int main()
{
    //スケジューラを初期化(省略可能)
    task_scheduler_init init;

    //データを用意
    int count = 10;
    concurrent_vector<int> v;
    for ( int i = 0; i <  count; i++ )
        v.push_back( i );

    //並列的に合計値を算出
    unsigned int total = 0;
    TotalValueComputer obj;
    parallel_for_each( v.begin(), v.end(), 
        [ &total ] ( const int value ) { total += value;
    });
    cout << "並列的に0~9の数値の合計を算出します。" << endl;
    cout << "合計値 = " << total << endl << endl;

    //逐次的に合計値を算出
    total = 0;
    for_each( v.begin(), v.end(), 
        [ &total ] ( const int value ) { total += value;
    });
    cout << "逐次的に0~9の数値の合計を算出します。" << endl;
    cout << "合計値 = " << total << endl << endl;

    return 0;
}


 このサンプルは、0~9の数値を合計する単純なものです。for_eachとparallel_for_eachが似ている事が確認できると思います。画面のスペースを節約する為に、単項関数オブジェクトを定義せずに、ラムダ式を使用しました。
 ただし、全く同じではありません。for_eachテンプレートは、戻り値を使用できますが、現在のところparallel_for_eachは戻り値を使用できません。例えば、parallel_doの記事で書いた、TotalValueComputerオブジェクトを使用する場合、for_each( v.begin(), v.end(), TotalValueComputer() ).GetTotal()と出来ましたが、現時点parallel_for_eachテンプレートはこれを出来ません。そして、使用法や使用するべき状況が事なります。
 今回の様な単純な処理でparallel_for_eachを使用するとパフォーマンスが低下します。また、parallel_for_eachは並列処理を行うので、データの整合性に注意せねばなりません。
 parallel_for_eachの正体は、parallel_do_feederを使用しないparallel_doです。parallel_doテンプレートは、複雑なオブジェクトによる、複雑な処理に使用するべきものですから、for_eachと同じ感覚で使用しないでください。parallel_for_eachは、慎重に使用しましょう。
 なお、parallel_for_eachは、Microsoft Parallel Patterns Library (PPL)と互換性を持つ為のものの様です。

【参考資料】

テーマ : プログラミング
ジャンル : コンピュータ

プロフィール

インドリ

Author:インドリ
みなさん、はじめまして、
コンニチハ。

ボクは、無限の夢(infinity dream)を持つネタ好きな虹色の鳥インドリ(in dre)です。
色々な情報処理技術を啄ばむから楽しみにしてね。

http://twitter.com/indori
は別人による嫌がらせ行為です。
私とは関係ないので注意して下さい。
次はなりすましブログなどをするかもしれませんが、ここ以外でブログをするつもりがないので、ここ以外にインドリのブログがあったとしても無視してください。


何度言っても分からない人がいるので、ここにコメント欄へ書き込むときの注意事項を書きます。


一、社会人としてのマナーをわきまえましょう。
一、妄想に基づく書き込みを止めてください。
一、暴言の類は書かないで下さい。
一、某誹謗中傷サイトの書き込みは彼らの妄想に基づく書き込みですから無視して、ここへ書き込まないで下さい。
一、コメント書く前に他のコメントよく読んでから行って下さい。
一、言いがかかり等の行為を禁止します。
一、その他常識的に考えて迷惑なコメントはしないで下さい。


以上のルールを守れない人のコメントは削除します。



利用上の注意
ここに紹介してある文章およびプログラムコードは正確であるように心がけておりますが、内容を保証するものではありません。当サイトの内容によって生じた損害については、一切の責任を負いませんので御了承ください。


執筆したCodeZineの記事


【VB.NETで仮想CPUを作ろう】

  1. VB.NETで仮想CPUを作ろう
  2. レジスタの実装
  3. 仮想CPUのGUI化
  4. テストドライバの改良
  5. CPUの基礎動作の実装
  6. MOV命令の実装
  7. ADD命令実装
  8. SUB命令実装
  9. INC命令&DEC命令の実装と命令長
  10. MLU命令の実装とModR/Mについて
  11. DIV命令の実装とイベント設計について
  12. 機械語駆動式 関数電卓を作ろう!
  13. 機械語駆動式 関数電卓を作ろう! 解答編(前半)
  14. 機械語駆動式 関数電卓を作ろう! 解答編(後半)


【仮想ネットワーク実装でTCP/IPを学ぼう】
  1. TCP/IPの基礎と勘所
  2. ネットワークアクセス層の勘所
  3. インターネット層の勘所
  4. トランスポート層の勘所
  5. アプリケーション層の勘所
  6. セキュリティの基礎と仮想ネットワークの仕様
  7. GDI+と独自プロトコルの定義



【並列化】
インテル Parallel Studioを使って並列化プログラミングを試してみた
並列プログラミングの効率的なデバッグを実現する「Parallel Inspector」


【TBBシリーズ】
  1. インテル スレッディング・ビルディング・ブロックの概要
  2. インテルTBBから学ぶループの並列化
  3. スレッドセーフとインテルTBBのコンテナ
  4. インテルTBBのスレッドクラス


【OpenMPシリーズ】
  1. OpenMPの基礎構文
  2. OpenMPの実行時ライブラリと並列ループ
  3. OpenMPのメモリモデルとfork- joinモデル

最近の記事
最近のコメント
月別アーカイブ
カテゴリ
Ada (9)
COBOL (5)
C (9)
C++ (11)
C# (370)
D (25)
Java (8)
Perl (1)
Ruby (14)
PHP (2)
Boo (2)
Cobra (2)
LISP (6)
F# (33)
HTML (0)
XHTML (0)
CSS (0)
XML (0)
XSLT (0)
Scala (4)
WPF (0)
WF (2)
WCF (0)
LINQ (4)
MONO (5)
Linux (0)
MySQL (0)
ブログ内検索
リンク
最近のトラックバック
RSSフィード
ブロとも申請フォーム

この人とブロともになる

QRコード
FC2カウンター