Chapter Eighty Three

第83話


最近Info-LabViewのメーリングリストで大きな配列を扱うときに配慮すべきことが話題になっていた。

以前Pictureコントロールの画像ファイル入力にTIFF版を作って知りあいにあげたところ、画像ファイルが大きくなるとメモリが不足して動かなくなるから使えなかったと言われたことがあった。彼はギガバイトのメモリを積んでいるので、いかに効率が悪いか驚いてしまった。そんな訳で、興味深く議論を見ていたのだが、NIのGreg McKaskleさんが分かりやすくまとめてくれていた。ざっと翻訳すると以下のような内容だった。訳が粗いので読みにくいかとは思うが、彼の言いたいことは伝えられていると思う。

1.大きなバッファだけを気にかけること。最悪なのは、全てのワイヤーについて最適化しようと悩むことだ。もしも、1メガバイトの配列を伝えるワイヤーがあり、それ以外のワイヤーが1キロバイト以下だとした時、1キロバイトワイヤーで誤差以上の成果を挙げようとすると多く最適化検討をこなさないといけなくなるので、1メガバイトのワイヤーに注意を集中するべきだ。

2.大きなバッファーは直列的に処理すること。LVはそれぞれのループにデータが到着したとき独立に処理や表示を行うなど、どんなタイプの並列処理でも簡単に実現することができる。このような並列処理を実現するためには、全ての中間状態が独立に存在するため独立なバッファーが必要となる。大きなバッファーが読み取られるあるいは読み取られ変更されるサブVIとすることは物事を単純にすることに役立つ。ワイヤーが分岐したりグローバルやローカルに書かれることがなくてLVが並列動作を行う必要がないならば、LVはより少ないバッファーでデータを保持することができる。

3.ワイヤーを分岐することと、グローバルやローカルを使用することはほとんど同じ意味合いを持つ。どちらの場合もデータは誰かが利用できるようにどこかに保存されている。ワイヤーは書き出しは一ヶ所に限られ、読み取られる場所はコンパイルする時点で全て分かっている。グローバルの場合はいつでも複数の書き出しと読み取りがメモリにアクセスでき、さらに同じエディタ/ランタイムの中の異なるLVアプリケーション同士でデータを共有できる。ローカルの場合はいくぶんグローバルよりは共有範囲が狭まるが、似たようなものだ。

4.Replace Array Elementは一般的に一時的な配列を使って配列の分割や結合を行うよりも良い。例えば、2次元配列で行あるいは列の交換を行うにはループの中で行う方が分割して再結合するよりも効率的だ。

5.ディスプレイはメモリのコピーを作る。グラフが前のデータをスクロールしたりズーミングを行ったりしながら表示しつつ、一方で次の値を計算できるためには、LVはデータのコピーを作る必要がある。ディスプレイがデータのコピーを作るのはパネルがメモリ内に存在するか属性ノードを持っている時だけだ。

6.サブVIバッファーはVIが呼び出されるごとに再利用される。もし、サブVIが配列の入力と1個か2個のループと配列出力を持っていてLVアプリケーションの4ヶ所で使われているとしたら、サブVIを再入実行に設定していない限り、必要な一時的なコピーは4回の呼び出しごとに再利用される。もし、再入実行に設定している場合は4個の呼び出しは並列に実行できるようにしなければならないので同時に独立な配列が必要となる。

もっと書き続けることができるが、一般的な処方箋は明らかだろう。並列処理には並列にバッファーが必要になる。表示と実行は並列して生じるので、、、。再入実行は並列実行を許可しているから、、、。ワイヤーの分岐やグローバルやローカルは並列実行できるから、、、。

並列処理が必要で大きな配列を扱っている場合はその配列にアクセスするコードを最小限にする必要がある。1個のサブVIを使ってユーザーが興味を持っている部分を返したり、処理を行って結果を返して、大きなバッファーにそのVIだけが触れるようにすることだ。一ヶ所以上で使われるサブVIで再入実行でないものは、パネルがメモリ上にあってディスプレイ用のデータコピーが行われない限り、一般的にメモリ使用量を削減する。

 

さて、上のようなGregさんの注意をもとに、以前に作ったプログラムを見てみた。

まず、データを読み込むシーケンスとデータの処理を行うシーケンスが別のシーケンスフレームにあるためにシーケンスローカルに大きなデータ配列を接続している。これは単に一つの画面に収めたかったために行っていたことだ。

次に、ワイヤーをたどっていくと、Flatten Pixmap.viに接続している。ファイルから読んだときは1次元のデータなのだが、Pixmapという少しだけ変わったピクチャーコントロールで使われる形式にするために、わざわざ2次元に変換してから、Flatten Pixmap.viにデータを渡しているのだ。pixmapは1、2、4、8、24ビットデータを扱うために少し変わった形式になっている。オンラインリファレンスには次のように書かれている。

「flattened pixmap data is a 1-dimensional array of 1-byte data that LabVIEW draws as a pixmap in the picture. The array is made up of a concatenation of rows of data. Within each row, data is packed into the array of bytes. If bit depth is 1, each byte represents 8 pixel values, if bit depth is 4, each byte represents 2 pixel values, and if bit depth is 8, each byte represents 1 pixel value.
Each row should be padded to a multiple of two bytes long.」(カギカッコ内は引用)

触らぬ神にタタリなし、とばかりに用意されている標準関数を使ったのだった。

さらに、YMCKデータを読み込みたいという希望だったので、色毎の2次元配列をページングした3次元配列も出力している。PNGやJPGのデータ形式ではCMYK画像データをLabVIEWに取り込めないので、TIFFファイル読み込みVIのメリットとなるので残すことにしよう。

Flatten Pixmap.viを開いてみると24ビットデータの処理は00RRGGBB HexのU32データをRR,GG,BBの順番で8ビット配列データにしているだけだった。マニュアルには横のバイト数は2の倍数と書いているのだが、24ビットでは特に何も行っていない。ということは、まったく無駄な処理をしていることになるのでCase3は入力と出力を直接つなげばいいことになる。ファイルから読み込む部分を整理して画像データにはシーケンスローカルも使うのをやめた。
幅350ピクセル、長さ400ピクセルの24ビット(3バイト)画像のデータサイズは420kバイトになるのだが、それぞれの場合で表示してメモリ使用状態を調べてみた。プロファイラーはLV6ではTools>Advanced>Profile VIs...にある。メモリ関係のチェックボックスをチェックして調べると、以前のVIは4355kBだったが、注意して作ったVIでは434kBと大幅に使用量が減少していた。こんなに変わるものかと驚いたね。

 

See you!

Nigel Yamaguchi


戻る