ふりーむ!BBS(閉鎖)
TOP > 過去ログ > 記事閲覧
掲示板一覧:[4 ゲーム開発・創作仲間募集(依頼先を探している方一覧)] [5 ゲーム・創作のお仕事募集(依頼を請け負っている方一覧)] (閉鎖:[1 初心者・質問] [2 ゲーム攻略] [3 ゲーム開発・創作の話題])

便利リンク1:[ふりーむ] [ゲーム制作ツール集(素材リンク集)] [素材ライブラリ]
便利リンク2:[イラスト投稿(サンプルやポートフォリオ用に)] [ふりーむプレミアムサービス(外部広告の削減を目指して取り組んでいます)]
下記掲示板は投稿の受付を終了しました(投稿できません)。一定期間後に公開も終了されます。2005年からご愛用頂きありがとうございました。なお、上記掲示板一覧の「4」「5」は現段階では「様子見」でしばらく投稿可能です。
fld_nor.gif オブジェクト指向から生じてきた「落とし穴」
投稿日 : 2008/05/20 10:09
投稿者 インステマスト
オブジェクト指向から生じてきた「落とし穴」--行列の掛け算で例える

ここでサンプル問題を示しています。
注意しておきたいのは、これは単なるサンプルに過ぎないのです。
さて問題は:「どうやって A=A*B って行列の掛け算をするのか」
当然目的は行列の掛け算自身ではないから、
大型プロジェクトのシステムに思う必要がありますからね。

もし、演算過程自体がオブジェクト指向でないなら、ふつうはこう:

#include "inst3DMath.h"

void Mat44MulMat44(MAT44 *ret, const MAT44 *mat1, const MAT44 *mat2)
{
ZeroMemory(ret,sizeof(MAT44));
for(Int32 i=0;i<=3;i++)
for(Int32 j=0;j<=3;j++)
for(Int32 s=0;s<=3;s++)
ret->m[i][j] += mat1->m[i][s] * mat2->m[s][j];
}
Mat44MulMat44( &result, &A, &B );
Copy( &A, &result );

あるいは、さらに可読性の高いものだと:

void Mat44MulMat44(MAT44 *ret, const MAT44 *mat1, const MAT44 *mat2)
{
for(Int32 i=0;i<=3;i++)
for(Int32 j=0;j<=3;j++)
{
Float tmp = 0;

for(Int32 s=0;s<=3;s++)
tmp += mat1->m[i][s] * mat2->m[s][j];

ret[i][j] = tmp;
}
}
Mat44MulMat44( &result, &A, &B );
Copy( &A, &result );

しかし、次のような風にしてしまったら、大間違いになるんですね:

void Mat44MulMat44(MAT44 *mat1, const MAT44 *mat2)
{
for(Int32 i=0;i<=3;i++)
for(Int32 j=0;j<=3;j++)
{
Float tmp = 0;

for(Int32 s=0;s<=3;s++)
tmp += mat1->m[i][s] * mat2->m[s][j];

mat1[i][j] = tmp;
}
}
Mat44MulMat44( &A, &B );

-------------------------------------------------------------------------
「当たり前だろ!俺を○○だと思ってんのかよ!?スレ主はこゆースレを見せ付けるつもりだったのか!」
----君はこう愚痴っていて…
-------------------------------------------------------------------------

さて、これがまじで大型プロジェクトにおける複雑なシステムであって、
行列の各々の要素が属性やメソッドを持っている小さなシステムである
ということだと考えてみましょう。。。なんて素晴らしいだろう。。。

#include <instDefines.h>

class CElement:virtual public IDynamic
{
private:
Int32 m_i,m_j; //自分の番号を記録!「オブジェクト指向によるカプセル化の利用!」
Float m_value;
public: //次の2行を無視すれば。。。
virtual void *GetAddr()const{ return (void *)this; }
virtual CStr GetClass()const{ return L"CElement"; }
public:
Float Read()const{ return m_value; }
void DoSomething( Int32 j );
};

いまなら、名前は変更していまして、
あの掛け算の関数も「DoSystem」と「DoSomething」という2つの関数になったんです。
行列は、「system1,2」になって。。。大型プロジェクトにおける複雑なシステムですからね。。。


CElement * * *system1, * * *system2; // 直ちにGlobalで - 便利上

void DoSystem()
{
for(Int32 i=0;i<=3;i++)
for(Int32 j=0;j<=3;j++)
{
system1[i][j] -> DoSomething(); //システム1の要素[i][j]に何かをやらせる。
}
}

void CElement::DoSomething()
{
Float tmp = 0;

for(Int32 s=0;s<=3;s++)
tmp += system1[m_i][s]->Read() * system2[s][m_j]->Read();

m_value = tmp ;
}

それでは、DoSystem()を呼び出すことにする。
その結果、プログラムは、ご希望の結果にならなかったわけです、
実際、クラッシュしてしまったとは言えますよね。。。


ある大型プロジェクトのテスト中に、こういう類のエラーなど大嫌いでしょうね、
そして、ふつうはハードコードして修正することにするわけですね。

こんなエラーには気をつけてください!
オブジェクト指向に迷わないように!


===================================================================================


行列の演算では、一時的な変数のresultで解決してやれるんですけど、
ほんまの大型システムになると、こういう方法は通用するまい。


さてここでは方法を幾つか紹介いたしますからね:

(一)シンプルスレッド + Update()

class CElement:virtual public IUpdatable
{
private:
Int32 m_i,m_j;
Float m_next, m_value;
public: //次の2行は無視してもOK
virtual void *GetAddr()const{ return (void *)this; }
virtual CStr GetClass()const{ return L"CElement"; }
public:
virtual void *Update(DWORD dt);
public:
Float Read()const{ return m_value; }
void DoSomething( Int32 j );
};

CElement * * *system1, * * *system2; // 直ちにGlobalで - 便利上

void DoSystem()
{
for(Int32 i=0;i<=3;i++)
for(Int32 j=0;j<=3;j++)
{
system1[i][j] -> DoSomething(); //システムの要素[i][j]を何かをやらせる。
}
}

void CElement::DoSomething()
{
Float tmp = 0;

for(Int32 s=0;s<=3;s++)
tmp += system1[m_i][s]->Read() * system2[s][m_j]->Read();

m_next = tmp;
}

void CElement::Update(DWORD dt)
{
m_next = ( m_value = m_next );
// その他の処理など...
}


===================================================================================


(二)“APRAM”計算モデル -- マルチスレッドまたは並列計算

これには2つのphaseを用いる必要があります。

 phase1 中に、tmpを計算し、
 phase2 中に、m_valueに書き込む。

phase1 と phase2 との間では「バリア同期」なんです。


ロジック的に言えば、ソフトウェア層では、Nのスレッド(プロセッサ)が必要で、
ハードウェア、OSが何だって、シンプルCPUか複数だって
設計思想は、同じなのです。

ただ、CPU数が必要なスレッド(プロセッサ)の数より少ない場合なら、
windowsシステムは「並列実行をシミュレートしてくれる」ってわけです。
だがこの「シミュレート」は、アプリケーションにとっては、透明なのです。

ここでは実際のコードを書き上げないことにして、
仮コードぐらい書いときますからね。


class CElement:virtual IRunnable
{
private:
Int32 m_i,m_j;
Float m_value;
public: //無視したらええわ
virtual void *GetAddr()const{ return (void *)this; }
virtual CStr GetClass()const{ return L"CElement"; }
public:
virtual void Run(); //スレッド(プロセッサ)のエントリ
public:
Float Read()const{ return m_value; }
void DoSomething( Int32 j );
};

CElement * * *system1, * * *system2; // 直ちにGlobalで - 便利上

void DoSystem()
{
for(Int32 i=0;i<=3;i++)
for(Int32 j=0;j<=3;j++)
{
system1[i][j] -> DoSomething(); //システムの要素[i][j]を何かをやらせる
}
}

void CElement::DoSomething()
{
Float tmp = 0;

for(Int32 s=0;s<=3;s++)
tmp += system1[m_i][s]->Read() * system2[s][m_j]->Read();

<Hold>(); //バリア同期、プログラムはここに止まってきて、
//全てのスレッド(プロセッサ)が<Hole>(仮コード)を呼び出すまで待つ

m_value = tmp;
}

void CElement::Run() //スレッド(プロセッサ)のエントリ
{
DoSystem();
}


じゃー、すべてのスレッド(プロセッサ)を起動し、プラットフォームなら何でも、
win32のマルチスレッド環境か、あるいは並列計算機でもいいです。

そうすると、このシステムが確かに正常に稼動できることは分かってきます。。。

さらに、もし並列コンピュータ持ってれば、
この行列の演算は、高速で並列的に処理される、これってかっこいいじゃないですか!?
編集 編集
件名 Re: オブジェクト指向から生じてきた「落とし穴」
投稿日 : 2008/05/28 08:42
投稿者 __
よかったですね^~^
編集 編集

フリーゲームライブラリ「ふりーむ!」へ

- WEB PATIO -