SSブログ

OS X用GigE Visionカメラドライバ - その33 [OS X用GigE Vision]

GenApiとGenTLの実装の前に、とりあえずGigEカメラのXMLファイルを自分で読んでカメラのレジスタをハードコードして画像を読めるようしたんだけど、安物のカラーカメラではBayer配列を受け取って自前でRGBに変換しないといけない。それをごく普通にCで書いたら1280x960ピクセルの30fpsでCPUコアをまるまるひとつ消費してしまった。こういうのはGPGPUにやらせるべき仕事なのでそうしたい。Core Imageのフィルタにでもあると簡単だったんだけど、そんなプリミティブなのはない。しょうがない、OS XのOpenCLを勉強しよう。他にあとで役に立つかもしれないし、ちょっとがんばろう。

ところで、こないだいかにも中の人っぽいコメントをもらって、いろいろ教えてもらえたらいいな、と思っていたんだけど返事がもらえない。どうやら通りすがりに上から目線で自分の言いたいことだけを投げ込んだみたいで、僕にとってあまり有益ではないらしい。まあ、誰もがみんな親切とは限らないのは当然だし、コメント主の指摘も要約すれば「好きにすればぁ」ということだと理解できるので、僕は僕なりに勝手に続けることにする。

前回はGenTLのAPIをおさらいしてみた。APIとしては素直なものでわかりやすく、少なくともGenTLを使う側からは何も難しいことはない。その上でさて僕はどうするか、というのを考えることにする。

7  GenTLに代わる実装

ということでGenApiの部分だけでは完結せずGenTLに対応する部分も実装する必要がある。ところがGenTLはあまりに一般的に設計されているため、GEN<i>CAMでいうトランスポートレイヤへのアクセスはRead()とWrite()しかない。そしてGenApiはユーザインターフェイスに直接対応しているため、トランスポートレイヤへのアクセスはユーザインターフェイスをブロックしてしまう。つまり最悪の場合、たとえばUDPパケットがロストした場合、Read()、Write()はタイムアウトするまで完了せず、ビーチボールが回ってしまうことになる。これが僕にとっての一番の問題。

ということで、この問題(Read()、Write()がブロックする)は、ドライバからユーザインタフェイスにまたがるレイヤの設計としては避けなければならない問題であって、少なくともOS Xの上でGigE Visionカメラを使う場合には望ましくない。

ということで、先日のコメントにもあったようにGenTLとの互換性を取る必要はないので、望ましい(というか、僕にとってマシな)
  • トランスポートレイヤへの読み書きは非同期に行う
  • GenApiツリーの変更は最小限にする
というような実装を考えてみよう。

7.1  トランスポートレイヤアクセス

ということで、トランスポートレイヤへのアクセスをブロックしない設計を考えてみる。さっきの話からGenTLとは互換性がない、したがって本来のGenApiとは動作が異なるということになる。別にそんなものは僕にはどうでもいいので勝手にやることにする。

素直に考えると、読み書きはリクエストだけして、完了したらコールバックしてもらう、というのが普通だろう。よくある問題で、OS XのFoundationのレベルでも何通りかの手段が提供されている。

たとえば
@protocol DGEGRequestingTransportProtocol
- (void)requestReadingInteger;
- (void)requestWritingInteger;
@end

@protocol DGEGAwareTransportReplyingPorotol
- (NSInteger)completeReadingInteger;
- (BOOL)completeWritingInteger;
@end
みたいに、値を読み書きする方はそのリクエストをする。それに対して読み書きが完了したら呼び出してもらう、という感じである。

これはCocoa/Objective-Cではよく使われる(とはいっても最近のOS Xでは使用頻度は低下してきているような気がする)デリゲートメソッドと同じような考え方である。

Objective-Cで書くと他にもやり方はあって
  • コールバックブロック
  • 通知(NSNotification)経由
  • KVO(Key Value Observing キーバリュー監視)
  • デリゲートメソッド、あるいはそれに似た実装(最初の例のもの)
などを使うというのがObjective-Cらしい書き方と言える。ブロックでコールバックを書くというのは一回きりなら読みやすい。でも今回の場合GenApiノード間でやりとりが行き交うことになるので、ブロックだらけになってしまう。

KVOもいい。KVOはやりとりするノード間でお互いを知る必要がない。しかしやりとりしたいデータの指定を文字列(キーパス)として表現する必要がある。その単なる文字列で表すということこそがKVOの利点なんだけど、それが今回の場合、文字列を生成するための手段が難しい。普通はその文字列をハードコードしてしまうんだけど、今回の場合ノードのプロパティの一部はObjective-Cのクラスのインスタンスとしてではなく、辞書として保持したい(使用頻度が低いものや複数通りの使われ方をするものは辞書にしたい)と考えているので、プログラマチックに生成しないといけない。もちろん辞書もKVOでアクセスできる(コレクション演算子を使うことで)けど、そのための文字列は長くなってしまう。ハードコードしているとわかりにくくなるので自動的に生成したい。そのためのクラスを書くことになるが、結構面倒な実装になる(辞書の内容によって型が違ったり、辞書にないプロパティをどう扱うかなど)。

たくさんのノード間でたくさんのやりとりがあるときには、通知が簡単でいい。ただしコードが分断しやすく、たくさん使いすぎると依存関係がわからなくなってしまう。また、デリゲートメソッド風というのは結局、KVOのメカニズムを文字列ではなく独自の方法で実装するようなもの。これも今回の場合目的が一つしかないので難しくなくてすっきりしている。

などといろいろ考えていると、デリゲートをもう少し拡張して複数のデリゲートを使えるようにしたコーディングが望ましい、という気がしてくる。

デリゲートメソッドというかコールバックメソッドの場合は、最初の例のようにリクエストだけを発信する。完了したらコールバックで教えて貰えばいいんだけど、そのときリクエストを受ける側は、どのオブジェクトからリクエストがあったか、ということを覚えている必要がある。それをやりたくなければ、一つのリクエストが完了するたびに、全部のノードに対してリフレッシュ動作をさせる、ということになる。変数の値の更新なのでそれほど大きなオーバーヘッドではないけど、あまり美しくない。

したがってリクエストをしたほうのオブジェクトは自分自身へのポインタをリクエストメッセージの中に含める、ということになる。たとえばこんなふうに。
@protocol DGEGRequestingTransportProtocol
- (void)requestReadingIntegerFromNode:(id)requetingNode;
- (void)requestWritingIntegerFromNode:(id)requetingNode;
@end

@protocol DGEGAwareTransportReplyingPorotol
- (BOOL)completeReadingIntegerRequestForNode:(id)requestedNode;
- (BOOL)completeWritingIntegerRequestForNode:(id)requestedNode;
@end
コールバックメソッド側にもリクエストを受け取ったオブジェクトを知らせると、まずコールバックでリクエストが成功したかどうかを知らせるだけにして、もし成功していたら値を問い合わせる、という手順を踏むようにすることができる。こちらのほうがエラーの内容ごとに処理を変えたりすることがやりやすい。

ところがこれではひとつ問題があることがわかる。リクエストを受けたほうのノードの値を参照しているノードが他にもある場合、その結果はリクエストしたノードだけにしか伝わらない。特に書き込みによって値が変わった場合、コヒーレンシを保つためには他のノードにも伝える必要がある。

そういう依存関係はXMLファイルの中に記述するようにGenApiではなっているようだけど、どうも市販されているカメラがすべてちゃんと記述しているようには見えない。少なくともカメラを買ってきて、そのXMLファイルを読むと、そういう依存関係の記述が不完全なものに出会う。正確にはGenTLのように同期的に読み書きする場合には問題無い、ということもありえるので一概に「不完全」とは言いきれない。

こういう場合には、初めからそれぞれのノードはどのノードが自分を参照しているかを知っていて、リクエストによって値が変わった時には他のノードに知らせる、ということを行う必要がある。これはKVOでも同じ問題が発生する。KVOではすなおに、オブザーバとして登録されているオブジェクトにすべて通知がされることになる。したがってKVOを使えば自動的にこの問題は解決する。

また、通知を使っても解決できる。通知の場合はノードに対して書き込みリクエストがあって、そのリクエストが変更されたら変更通知をポストすればいい。そのノードを参照しているノードはその通知のオブザーバとして自分自身を登録しておけばいい。

通知の問題は
  • どのノードの何の値なのかということを通知名(NSNotification.name)として表す必要がある
  • 通知はオーバーヘッドが大きい
である。通知名はシステマティックにつけられないといけないので、自動的に生成できないといけない。これはKVOを使う場合と同じ問題である。

また、通知はもともと恒常的に発生させるものではなくて、なにか特定のイベントが発生したことを知らせるためのものだった。そのため実行効率よりも使い勝手を優先して設計されている。

というふうに色々考えると、コールバックメソッド型で、値に変更があったときはKVOのようにすべての参照元に対して通知するような独自実装するのが結局いい、ということになる。一番面倒な結論。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。