2016/04/18

[C#]BinaryReaderのReadInt32が遅いとき

このエントリーをはてなブックマークに追加

ファイルの読み書きを高速化する

テキストファイルを読み書きするにはStreamReader/StreamWriterを使えばいいのでお手軽です。
テキストファイル以外のバイナリファイルだったり、すべてを読み込むわけにはいかない大きなファイルを扱うときには、BinaryReaderを使うことになるでしょう。

また、繰り返しReadInt32を繰り返し呼んでいるところは、その度にじりじりとファイルアクセスしています。これが首を絞めるということはあまりないかもしれませんが、ファイルアクセスはメモリアクセスと比較すると当然ながら低速です。
速いに越したことはないはずなので、高速化していきましょう。
改善案としては、ReadInt32メソッドを繰り返し呼ぶところを、ReadBytesメソッドで一気読みしてからBitConverter.ToInt32で分離させます。

BitConverterとは
BitConverterはバイナリ配列(byte[])の必要なところを、型変換してくれるクラスです。
BitConverter.ToInt32メソッドは、バイナリ配列と、開始位置を指定してあげれば、その部分のint値が取り出せます。

ReadInt32だけでなく、ほかにもReadDoubleメソッドやReadSingleメソッドも用意されているので、同様の改善ができそうです。
ただし、ReadStringメソッドは、最初からデータサイズ込みの可変長なので、もう一つ奥のほうに手を伸ばさないと難しそうですね。

部分的なまとめ読みができる
データファイルが大きくて、そして全部の情報はいらないというときに、部分的にまとめ読みできるというワザができるわけです。
読み込みだけでなく、書き込みについても同様です。
当方、あんまりファイルアクセスに関わるコードを書かないので勉強になりました。

大きさが定まっていないデータの構造は、必要なところだけを読み込み/書き込みできるように工夫しておくほうが効率的ということですね。

具体例

具体的なサンプルとして、GraphicsPathのパスデータ(PathData)を書き出したバイナリファイルを読み込む処理を考えることにします。
パスデータ(PathData)の座標(Point)の配列をたっぷり持っています。

呼び出し側のコード
using System.Drawing;
using System.IO;

// 呼び出し側

fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
binaryReader = new BinaryReader(fileStream,Encoding.UTF8);

// 他の処理

int pointCount = binaryReader.ReadInt32(); // パス数
Point[] points = this.ReadPathPoints(binaryReader, pointCount);

1.BinaryReader.ReadInt32で読み込むコード
// ReadPathPointsメソッド
private Point[] ReadPathPoints(BinaryReader binaryReader, int pointCount) {
    Point[] points = new Point[pointCount];
    for (int i = 0; i < pointCount; i++) {
        int x = binaryReader.ReadInt32();
        int y = binaryReader.ReadInt32();

        points[i] = new Point(x,y);
    }
    return points;
}

2.ReadBytesとBitConverter.ToInt32で読み込むコード
// ReadPathPointsメソッド
private Point[] ReadPathPoints(BinaryReader binaryReader, int pointCount) {
    byte[] b = binaryReader.ReadBytes(pointCount << 3);
  
    Point[] points = new Point[pointCount];
    for (int i = 0; i < pointCount; i++) {
        int x = BitConverter.ToInt32(b, i << 3);
        int y = BitConverter.ToInt32(b, (i << 3) | 4);
    
        points[i] = new Point(x,y);
    }
    return points;
}


ビット演算を使っていますが、ビット演算による高速化の恩恵は誤差のレベルです。

速度評価とまとめ

ざっくり処理速度の評価です。
改良ポイントの評価としてはややアバウトですので、参考程度に思ってください。

ReadInt32呼び出しのときは、2388msでした。
改良して669msになりました。処理時間が1/4になりました。

やったね。

ということで、やや限定的ではありますが、左から右に負荷軽減できるテクニックとして勉強になりました。
おしまいです。

バイナリ・ファイルを読み書きするには?[C#、VB]
http://www.atmarkit.co.jp/fdotnet/dotnettips/669bincopy/bincopy.html

0 件のコメント :

コメントを投稿