次期グラフシミュレータ(GraphSim3)に関する考察(3)
前回は,問題点を挙げた.問題点は,次の通りです.
- 不必要なコンストラクタ
- 入出力関数でのポート指定
- 入出力データの扱いづらさ
- 無駄なtry{}catch{}構文
- InterruptedExceptionの記述
- ステートマシンの分かりづらさ
- DefaultFunctionalModule
そして,1番目の不必要なコンストラクタについて述べた.今回は,2番目の入出力関数でのポート指定について述べる.
入出力関数でのポート指定
GraphSim2では,ポート指定に文字列を使う.こんな具合だ.
DataObject in = read( "input" ); write( "output1", in ); write( "output2", in );
read関数はポート名を引数を受け取り,そして指定されたポートに入力されているデータをFIFOで取り出す.write関数は,ポート名とデータオブジェクトを受け取り,指定されたポートからデータをFIFOで出力する.
この文字列によるポート指定の問題を挙げると,大きく次の二つがある.
- 間違えやすい
- 速度
間違えやすい
まず,「間違えやすい」の一言に尽きる."input"を"imput"と打ってしまうかもしれないということだ.GraphSim2では,このような問題が起きてしまったらどうするかというと,エラーメッセージを表示して,シミュレーションを中止する.このポート指定の間違いは,シミュレーションを開始してみなければ分からないところに問題がある.すなわち,間違ってポート名を指定している式を評価するまで,このバグは発見されないということだ.これでは不便だ.
このようなケアレスミスを防ぐためには,入力補完に代表されるエディタサポートが一番有効だと思っている.eclipse等でこの入力補完を有効にするためには,文字列ではなく,シンボルでポート名を指定しなければならない*1.そのため,私は自分で機能回路を定義するとき,次のような記述を用いている.
public class Tap extends DefaultFunctionalModule { public static final String IN = "in"; public static final String OUT0 = "out0"; public static final String OUT1 = "out1"; public static final String[] ipNames = { IN }; public static final String[] opNames = { OUT0, OUT1 }; public ThroughBetween(){ super( ipNames, opNames ); } public void exec() throws InterruptedException { try{ DataObject in = read( IN ); write( OUT0, in ); write( OUT1, in ); }catch( PCAException e ){ e.printStackTrace(); } } }
このような記述を用いることで,タイプミスというケアレスミスはほとんどなくなる.なぜならば,INやOUT0,OUT1のタイプミスを起こしても,コンパイル時に「そんなシンボルはないよ」というエラーが発生するからだ.
しかし,これは記述しなければならない項目を増やすことになり,少しばかりメンドクサイ.
速度
次に,速度の問題がある.
文字列を用いてポート名を指定しているということは,内部で,ポート名とデータ入出力用オブジェクトとのマッピングを行っていることを意味する.現在は,HashMapを用いている.
GraphSimでの機能回路は細粒度で記述されることが多い.これは,処理に対して,入出力の回数の比率が多いということを意味する.私が取った簡単なベンチマークでは,全体の処理時間におけるread/write関数の処理時間の割合は,最もひどいもの90%を超え,平均しても30〜40%はread/write関数だ.これは,入出力の方法を改良することで,すぐさま処理速度を1.5倍にできることを意味している.
まとめ
現在,上記の二つの問題点を解決できるポートの指定方法を考えている.
上記の問題を同時に解決するために,ポート指定に文字列ではなくシンボルを用い,さらにread/write関数とポートを静的に結びつけるための仕組みが必要である.
現段階で私が考える理想的な記述は,次のような感じである.
入出力ポートをシンボルを用いる場合.
DataObject in = IN.read(); OUT0.write( in ); OUT1.write( in );
各ポート専用の入出力関数を定義する場合.
DataObject in = readIN(); writeOUT0( in ); writeOUT1( in );
一つ目の記述をサポートするためには,ポートのためのシンボル定義が必要である.二つ目の記述をサポートするためには,一つ目と同様,入出力関数の定義が必要である.これらの共通の問題は,シンボル定義をどこで行うのかであり,これがGraphSim3を実装するうえで重要なポイントとなる.
*1:文字列の保管を有効にするようなエディタを書けば良いというのはなしね