Chapter4.9-4.12 6311669 藤田晋哉 @M1 in武田研 入力ストリームの利用 • 入力ストリーム上で二つの操作が行われる – ストリームの読み込み – ストリームのEOSチェック 入力ストリームの利用 • ストリームのEOSチェック – ストリームの先頭の要素をチェック →ストリームの終わりであるならTrue →終わりでないならストリームが開いているのを 示しながらFalseを返す. 入力ストリームの利用 • ストリーム要素の読み込み – 読むべきストリームが空であるなら次のストリー ムの要素を呼び出す. – 閉じられたストリーム上でリードオペレーションを 実行した場合はtrueを返す. -> 返り値を用いてループ記述のが一般的 while(co_stream_read(input_stream) == co_err_none) { //ループしたい処理 } 入力ストリームの利用 • ストリームを閉じる(co_stream_close) – 入力ストリームがco_stream_closeで閉じられたと き、それ以降のデータは破棄されてEOSトークン が消費される. – EOSがストリーム上にない場合は co_stream_closeはブロックされる. ライタが意図しない終了を防ぐ. (読む側からはEOSトークンを検出するまでは ストリーム を閉じない) EOS検出 • Eosを検出する(co_stream_eos) – ライターがストリームをとじたらco_stream_eosは Trueを返す. – 一回Falseを返したら,リーダがストリームを閉じる まではその後で呼び出されたco_stream_eosは Falseを返す. – co_stream_eosはノンブロッキングなのでそれ以 降もストリームへのアクセスは可能. – co_stream_readの呼び出しもそれ以降は co_err_eosを返す. (↑これをループ記述に使える) ストリームの効率的な利用 • 効率的にストリームを利用するには ImpulseCを使ってかっこいいプログラミング をすることが重要. ストリームの効率的な利用 • パターン1(残念な例) while(!co_stream_eos(input_stream)) { co_stream_read(input_stream,&i,sizeof(i) ); …...// 処理の内容 } {EOSはないか?→co_stream_read→処理} この構成はあまり良くない ストリームの効率的な利用 • パターン1(残念な例) while(!co_stream_eos(input_stream)) { この間でEOSが呼び出されたらまずい co_stream_read(input_stream,&i,sizeof(i) ); …...// 処理の内容 } co_stream_eosとco_stream_readの間で送り手 からストリームが閉じられるかも. →リードバッファに有効な値が格納されない. ストリームの効率的な利用 • パターン1(残念な例) while(!co_stream_eos(input_stream)) { co_stream_read(input_stream,&i,sizeof(i) ); ……// 処理の内容 } この部分で値をチェックすればいいね ストリームの効率的な利用 • パターン2(大丈夫な記述) while(!co_stream_eos(input_stream)){ if(co_stream_read(input_stream, &i, sizeof(i)) ==co_err_none){ ……//処理の内容 } } co_stream_readで値のチェックを行うようにした. とりあえず問題のない記述.けど冗長 ストリームの効率的な利用 • パターン3(好ましい書き方) if ( co_stream_read(input_stream, &i, sizeof(i)) == co_err_none ) { do{ ……// 処理の内容 } while( co_stream_read(input_stream, &i, sizeof(i)) == co_err_none); } 処理サイクルが早くなるパターン ストリームの効率的な利用 if ( co_stream_read(input_stream, &i, sizeof(i)) == co_err_none ) { do{ // 処理の内容 } while( co_stream_read(input_stream, &i, sizeof(i)) == co_err_none); } while(!co_stream_eos(input_stream)){ if(co_stream_read(input_stream, &i, sizeof(i)) ==co_err_none){ //処理の内容 } } ReadのチェックがWhile内に含まれないので早くなる. ストリームの効率的な利用 • パターン4(好ましい記述) while(co_stream_read(input_stream) == co_err_none){ …… // 処理の内容 } co_stream_eosを明示的に呼び出すのを除去 した形. ストリームの効率的な利用 while(co_stream_read(input_stream) == // 処理の内容 } co_err_none){ while(!co_stream_eos(input_stream)){ if(co_stream_read(input_stream, &i, sizeof(i)) ==co_err_none){ //処理の内容 } } 集積回路の面積がコンパクトですむ. ストリームの効率的な利用 回路面積 速度 例2 大きい 遅い 例3 大きい 速い 例4 小さい 遅い ストリームデッドロックの回避 • ストリームのデッドロックは他のプロセスがタ スクを終えて、データを出力に書き込むまで 一つのプロセスが進まない時に起きる • 不規則なデータを扱うシステムやサイクルの 遅延が変化しやすいシステムでは厄介な 問題. • バッファの深さを増やすと解決したかのよう に見えることがあるけど実際はバッファの深 さで解決するケースはすくない ストリームデッドロックの回避 • デッドロックの例 四つの値を逆順にして返す Supervisor – 四つの値をストリームS1に送る. 逆順になって帰ってくる四つの値 をS2からread. Worker –S1からreadした値をS2に逆順でwrite (LIFO) ストリームデッドロックの回避 • Supervisor for ( i = 0; i < iterations; i++ ) { for ( j = 0; j < 4; j++ ) { local_S1 = rand(); printf("S1 = %d\n", local_S1); co_stream_write(S1, &local_S1, sizeof(uint32)); //S1に四つの値をwrite } for ( j = 0; j < 4; j++ ) { co_stream_read(S2, &local_S2, sizeof(uint32)); //S2から四つの値をread printf("S2 = %d\n", locai_S2); }} ランダムに値を選ぶ(&出力)→S1にwrite →S2をread ストリームデッドロックの回避 • Worker while (!co_stream_eos(S1)) { for (i = 0; i < 4; i++) co_stream_read(S1, &data[i], sizeof(uint32)); //S1から四つread for (i = 0; i < 4; i++) co_stream_write(S2, &data[3-i], sizeof(uint32));//S2に四つwrite } Workerプロセスは S1の四つの値をread→S2に四つの値をwrite ストリームデッドロックの回避 Supervisor local_S1 = rand(); 1234 S1 S2 ストリームデッドロックの回避 Supervisor co_stream_write(S1, &local_S1,sizeof(uint32)); 4 3 2 1 S1 S2 ストリームデッドロックの回避 Worker co_stream_read(S1, &data[i],sizeof(uint32)); S1 1234 S2 ストリームデッドロックの回避 Worker co_stream_write(S2, &date[3-i],sizeof(uint32)); S1 S2 1234 後ろの要素から ストリームデッドロックの回避 Worker co_stream_write(S2, &date[3-i],sizeof(uint32)); 4 3 2 1 S1 S2 ストリームデッドロックの回避 Supervisor co_stream_read(S2, &local_S2,sizeof(uint32)); 4321 S1 S2 ストリームデッドロックの回避 • SuperVisor:パケット長に依存しないように SuperVisor for ( i = 0; i < iterations * 4; i++ ) { local_S1 =rand(); printf("S1 = %d\n", locai_S1); co_stream_write(S1, &locai_S1, sizeof(uint32)); co_stream_read(S2, &locai_S2, sizeof(uint32)); //デッドロック printf("S2 = %d\n", locai_S2); } この場合はデッドロックする. ストリームデッドロックの回避 Supervisor local_S1 = rand(); 1 S1 S2 ストリームデッドロックの回避 Supervisior co_stream_write(S1, &local_S1,sizeof(uint32)); 1 S1 S2 ストリームデッドロックの回避 Worker co_stream_read(S1, &data[i],sizeof(uint32)); S1 S2 1 ストリームデッドロックの回避 • Worker while (!co_stream_eos(S1)) { for (i = 0; i < 4; i++) co_stream_read(S1, &data[i], sizeof(uint32)); //S1から四つread for (i = 0; i < 4; i++) co_stream_write(S2, &data[3-i], sizeof(uint32));//S2に四つwrite } Workerは四つreadするまでは進まない ストリームデッドロックの回避 SuperVisor for ( i = 0; i < iterations *4; i++ ) { local_S1 =rand(); printf("S1 = %d\n", locai_S1); co_stream_write(S1, &locai_S1, sizeof(uint32)); co_stream_read(S2, &locai_S2, sizeof(uint32)); //デッドロック printf("S2 = %d\n", locai_S2); } ストリームデッドロックの回避 Worker→S1から次の値がくるのを待ってる SuperVisor→S2に値がくるのを待ってる -> デッドロック S1 S2 1 ストリームデッドロックの回避 • ストリームを用いる際は設計に注意 – プロセスの問題とストリームの同期を常に考慮 しておかないといけない. – 大きな再設計なしで解決する場合もある. NonBlockingなストリーム読み込み • SuperVisor側は通常,パケットの長さに依存 するべきではない. – 任意の長さの出力を求められるのが普通. →ブロックされないストリーム読み込み co_stream_read_nbを使うと捗る. NonBlockingなストリーム読み込み SuperVisor for ( i = 0; i < iterations *4; i++ ) { local_S1 =rand(); printf("S1 = %d\n", locai_S1); co_stream_write(S1, &locai_S1, sizeof(uint32)); while(co_stream_read_nb(S2, &locai_S2, sizeof(uint32))) printf("S2 = %d\n”, locai_S2); } S2の読み込みをブロックしないようにしておく. NonBlockingなストリーム読み込み Worker→S1から次の値がくるのを待ってる SuperVisor→S2に値がないので次へ S1 S2 1 NonBlockingなストリーム読み込み Supervisior co_stream_write(S1, &local_S1,sizeof(uint32));に戻る 2 S1 S2 1 NonBlockingなストリーム読み込み Worker co_stream_write(S2, &date[3-i],sizeof(uint32)); S1 S2 1234 NonBlockingなストリーム読み込み Worker co_stream_write(S2, &date[3-i],sizeof(uint32)); 4 3 2 1 S1 S2 NonBlockingなストリーム読み込み Supervisior while (co_stream_read_nb(S2, &locai_S2, sizeof(uint32))) S2が空になるまで読み込み S1 4321 S2 NonBlockingなストリーム読み込み • データ長に依存しないようなループを記述す る場合は,co_stream_read_nbを用いると解決 することが多い. • ポーリングしていくことになるので大抵の場 合で速度は落ちる デッドロックとPIPELINE • PIPELINEを使うとデッドロックの状態をシ ミュレートすることができる. • 並列化の時にPIPELINEによる複数の繰返 しを使う事が多い. →PIPELINEについてはもっと後に詳細 (別のChapter) シグナルの作成と利用 • インパルスCはプロセスとストリームの間の データ移動を少なくすることが特徴の一つ. • ストリームを用いてなるべく少ない数の入力 と出力でプロセス間の同期をとることができ る. シグナルの作成と利用 • 同期をとる方法の一つとしてシグナル. • シグナルでメッセージパッシングが可能. シグナルread→co_signal_wait(ブロックする) シグナルwrite→co_signal_post(ノンブロッキング) • 送るプロセスはco_signal_postを呼び出して受 け取るプロセスはco_signal_waitを呼び出す シグナルの作成と理解 一つの入力ストリームで同期を行う例 do { for (i =0; i < 8; i++) { if (co_stream_read(stream_input, &data, sizeof(uint32)) ==co_err_none) co_stream_write(stream_output, &data, sizeof(uint32)); else printf("Unexpected end of data stream detected in proc1.\n"); } co_signal_post(ready_signal_output, i); co_signal_wait(ack_signal_input); } while (co_stream_eos(stream_input) ==co_err_none); シグナルの作成と理解 8個の値をstream_inputから読み込んでstream_output に流す.(これ自体に多分深い意味は無い) do { for (i =0; i < 8; i++) { if (co_stream_read(stream_input, &data, sizeof(uint32)) ==co_err_none) co_stream_write(stream_output, &data, sizeof(uint32)); else printf("Unexpected end of data stream detected in proc1.\n"); } co_signal_post(ready_signal_output, i); co_signal_wait(ack_signal_input); } while (co_stream_eos(stream_input) ==co_err_none); シグナルの作成と理解 co_signal_postでメッセージのポスト(8個の値の出力 が終わった事を知らせる) do { for (i =0; i < 8; i++) { if (co_stream_read(stream_input, &data, sizeof(uint32)) ==co_err_none) co_stream_write(stream_output, &data, sizeof(uint32)); else printf("Unexpected end of data stream detected in proc1.\n"); } co_signal_post(ready_signal_output, i); co_signal_wait(ack_signal_input); } while (co_stream_eos(stream_input) ==co_err_none); シグナルの作成と理解 co_signal_wait で他のプロセスからのメッセージを待 つ. (メッセージが来たら次の値をセット) do { for (i =0; i < 8; i++) { if (co_stream_read(stream_input, &data, sizeof(uint32)) ==co_err_none) co_stream_write(stream_output, &data, sizeof(uint32)); else printf("Unexpected end of data stream detected in proc1.\n"); } co_signal_post(ready_signal_output, i); co_signal_wait(ack_signal_input); } while (co_stream_eos(stream_input) ==co_err_none); シグナルの作成と理解 • co_signal_waitはブロックされる. →メッセージを受け取るまではプロセスを続け ることはできない. • co_signal_postはノンブロック. →メッセージが受け取られるまで待ったりはし ない.(メッセージが存在するときにpostしなおす とメッセージの値は上書きされる.) レジスタの理解 • ImpulseCで最も一般的な同期はストリーム を使うこと. • アプリケーションの要求やハードウェアに依 存してしまうが,共有メモリやシグナルを使う 事も可能. →データの同期,バッファリングが可能. レジスタの理解 • 共有メモリやシグナルの利用 – 低レベルでのプロセス間の同期をプログラマは 考慮することなく高度に並列化されたシステム の記述が可能になる. – ただし,データセントリックな記述がされる必要が ある. レジスタの理解 • 多くのアプリケーションは入力と出力を非同 期で出力する. • 対応したハードウェアデバイスやシグナルに 直接インターフェースに接続するアプリケー ションもあるが,二つの独立したハードウェア プロセス間を非同期で接続する必要がある 場合もある. レジスタの理解 • このような場合のために,ImpulseCにはハー ドウェアの有線接続に対応したco_register データオブジェクトを持っている. • ストリームやシグナルと同様に,レジスタは co_register_createで作成されてアプリケー ションのコンフィグレーションで使われる. レジスタの理解 • コンフィグレーション内で void config_counter(void *arg){ co_register counter_dir; co_register counter_val; counter_dir = co_register_create("counter_dir", UINT_TYPE(1)); counter_val = co_register_create(''counter_val” ,INT_TYPE(32)); …… counter_dirとcounter_valという名前でレジスタが 作成される. レジスタの理解 co_process main_process; co_process counter_process; main_process = co_process_create("main_process", (co_function)counter_main, 2, counter_dir, counter_val); counter_process = co_process_create(''counter_process'', (co_function)counter_hw, 2, counter_dir, counter_val); } counter_dirとcounter_valの二つのレジスタで二つの プロセスを接続している. (counter_dir:バッファリングを必要としない入力 counter_val:バッファリングを必要としない出力) レジスタの理解 • コンフィグレーションで作成したプロセス内で co_register_get, co_register_put, co_register_write, co_register_read, を使ってレジスタの値にアクセスしたり値を書 き込んだりすることができる. レジスタの理解 void counter_hw(co_register direction, co_register count) { Int32 nValue = 0; //記憶装置 while ( 1 ) { if (co_register_get(direction) == 1) //入力レジスタが1なら nValue++; else nValue--; co_register_put(count, nValue); //countレジスタに出力 }} 単純な32bitのアップダウンカウンターの例. direction,countレジスタは外部デバイスからの入力と 出力にあたる. ソフトウェアプロセス側のレジスタ操作 • ほとんどの場合(全部),レジスタはハードウェ ア間の伝達や同期を行う際に使われる. • ハードウェアとソフトウェアプロセスの接続も とりあえず可能. – ソフトウェアプロセスにステータス情報を送る. – ソフトウェアプロセスからハードウェア設定を送 るなど ソフトウェアプロセス側のレジスタ操作 • Visual Studioにあるように,アプリケーション (レジスタの接続なども含めて)のテストを行う ために,ベンチマークテストをするソフトウェア を作成することは可能ではある. – 即ち,ソフトウェアプロセスからレジスタへの読み 書き →プロセスの順番や状態などがそのOSやデ バッガでうまくいっただけ. 他の環境でも動くことは保証されない. ソフトウェアプロセス側のレジスタ操作 • レジスタを実装するときに,一つだけレジスタ に書き込みを行うプロセスが存在しなければ ならない. – 双方向レジスタの概念はない. • 複数のプロセスがレジスタにco_register_put やco_register_writeを行うとハードウェアコン パイラはエラーを起こす. • この状態はソフトウェアシミュレーションで 検出できるかわからない おわりました ご清聴ありがとうございました.
© Copyright 2024 ExpyDoc