藤田 晋哉(4.9-12)

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を行うとハードウェアコン
パイラはエラーを起こす.
• この状態はソフトウェアシミュレーションで
検出できるかわからない
おわりました
ご清聴ありがとうございました.