LabVIEW で高速にストレージに書き込む (3) - 高速に書き込めた!

前回の LabVIEW で高速にストレージに書き込む では、データを生成するのが遅いためストレージへの書き込みが遅いのでは?と考えていましたが、どうも違ったようです。

前回のブロックダイアグラム

clip_image001[5]

上の図が前回のブロックダイアグラムです。データの書き込みは手抜きをしていて、while ループを待機無しでぶん回しています。もちろん CPU 1コア分 100% の使用率になってしまいます。

停止ボタンが押されたら書き込んだファイルサイズから、転送レートを割り出すという仕組みです。

今回の成果

まずは今回の成果から。

対 RamDisk で 2575MB/sec の書き込みスピード

対 RAID で 575MB/sec の書き込みスピード

を達成しました。

対 RamDiskclip_image002[4]

対 RAIDclip_image003[4]

何が良かったのか?

では一体何が書き込み速度向上の秘訣だったのでしょうか?

  1. 書き込み担当のスレッド(VI) を用意した
  2. ファイルオープン時にバッファを無効にした
  3. データ書き込み時にバイトオーダーを little-endian にした
  4. データ書き込み時にデータサイズを追加しないようにしたこの以上4点です。この結果は以下の通り。

全パターンは試していませんので、代表的なものを表にしました。

バッファ 有効 無効 無効 有効
データサイズ追加 しない しない しない する
バイトオーダー little little big big
結果(MB/sec) 946.12

2426.56

586.322

419.551

最速の場合で 6倍程度差がついています。

オーダーを little→big にしただけで 2426→586MB に落ちていますから、オーダー変換にかなりのオーバーヘッドがかかっているものと思います。

今回のブロックダイアグラム

今回はあまり手抜きをせず実装してみました。

メインのスレッドと、書き込みを担当するワーカスレッドに分けました。

メイン側で送信データの作成と、キューを経由してワーカスレッドにデータを投げます。ワーカスレッドは、キューに入ったデータを淡々とファイルに書き込むだけという流れです。

メインスレッドclip_image004[4]

ワーカースレッド(ファイル書き込み担当)clip_image005[4]

バッファを無効にする

ファイルを開くときに、バッファ有効/無効の設定が出来ます。LabVIEW 内部で読み書きのバッファを用意するかどうかを設定するようです。

clip_image006[4]

今回のブロックダイアグラムではキュー自体がバッファの役割を果たしているので、バッファを無効にしてしまいましょう。一つのストリームでダブルバッファリングしても意味ないですから。

バッファリングの細かいところは LabVIEW ヘルプの、

上級ファイルI/O操作の関数を使用する→ファイルのバッファ処理

に書かれています。

重要な点は、

ファイル内のデータのサイズは、ファイルを含む(またはファイルを含む可能性のある)ディスクのセクタサイズ(バイト単位)の倍数にしなければなりません。セクタは、定量のデータ(通常、512バイト)を保存するディスク領域の分割された一部分です。「ボリューム情報を取得」関数のセクタサイズ(バイト)出力を使用して、ディスクのセクタサイズを決定します。LabVIEWがデータをディスクに保存するためには、データは複数のセクタに及ぶことができますが、それぞれのセクタが完全に満たされる必要があります。512バイトセクタは、512バイトのデータを必要とします。データがセクタサイズの倍数でない場合、データをフィルタデータでパッドして、LabVIEWがファイルを再度読み取る前に、フィルデータを削除する必要があります。

このあたりかと。

配列または文字列サイズを先頭に追加?

LabVIEW ヘルプには以下のように説明があります。

clip_image007[4]

LabVIEW ヘルプは色々書かれているんですが、例が記載していないため良く分からないですね。(LabVIEW に限らずですが)

分からないのはイヤなので、実際にファイルに出力して比較しましょう。

True の場合 ( デフォルト )clip_image008[4]

False の場合 ( 今回の設定 )clip_image009[4]

確かに true と false を比較すると、先頭 4バイト分ずれているので True のデータ先頭は U32 あたりでデータサイズが書かれているのでしょう。

で書かれている値は "00-A0-0F-00"。リトルエンディアンなので、"00-0F-A0-00"。10進数にすると、1024000。

今回は1回で U64 を 1024000ヶ書き込んでいるので、先頭の U32 はデータの個数が書かれていることが分かりました。

今回の書き込むデータは固定長なので、データの個数を書き込んでおく必要は無いので、false で良いですね。

ちなみにデータサイズの追加は「バッファを有効」にしないとエラーが発生します。バッファ内で追加して書き込みを行なっているものと思います。

バイトオーダー

バイナリファイルを書き込む際にバイトオーダーを指定することが出来ます。データの受け渡しがネットワーク越しだったり違う OS だったりする際には、こういう機能があると便利ですが、デフォルトが big-endian というのはいかがなものでしょう?(LabVIEW が Mac から始まった名残なのでしょうか。)

clip_image010[4]

Windows は little-endian なのでデフォルトの big-endian だとデータを書き込むたびに、

big-endian → little-endian の変換

を行なって CPU パワー食いそうですね。

ここも Windows で使う分には little-endian にしておきましょう。ちなみに設定できる3種類のデータを取っておきました。host order は、Windows なら little, MacOS なら big に成るのだと思います。

Big-Endianclip_image011[4]

little-endianclip_image012[4]

native, host order (=little-endian)clip_image013[4]

まとめ

上級ファイルI/O関数のちょっとしたパラメータで、全然ストレージスピードが変わってしまいます。

  • ファイルを開く関数では、バッファリング無効
  • バッファリングはキューを使って VI に流す
  • バイトオーダーは OS 標準に合わせる (Windows なら little)
  • データサイズの追加書き込みはしない

この4点を守ると最大6倍高速に書き込みできることが分かりました。

実際のデータの書き込みには「一度に書き込むデータサイズ」などの要因で大きく左右されますが、それは次回!

コメント