4

これは、私が掘り下げてきた個人的なプロジェクトです。基本的に、StreamReader を使用してテキスト ファイル (20 MB から約 1 GB まで) を解析します。パフォーマンスはかなりしっかりしていますが、それでも... バイナリで解析するとどうなるか知りたくてうずうずしています。誤解しないでください。私は時期尚早に最適化しているわけではありません。私は間違いなく、「見る」ためだけに意図的にマイクロ最適化しています。

そのため、バイト配列を使用してテキスト ファイルを読み込んでいます。調べてみると、新しい行は (Windows) 標準の CR/LF または CR または LF になる可能性があります...かなり面倒です。CR で Array.IndexOf を使用して、LF をスキップできるようにしたいと考えていました。代わりに、IndexOf と非常によく似たコードを書いていますが、いずれかをチェックし、必要に応じて配列を返しています。

要点: IndexOf と非常によく似たコードを使用しても、私のコードは依然として非常に遅くなります。800MB のファイルを使用して全体像を把握するには、次のようにします。

  • IndexOf を使用して CR を探す: ~320mb/s
  • StreamReader と ReadLine の使用: ~180mb/s
  • for ループ複製 IndexOf: ~150mb/s

for ループ (~150mb/s) を含むコードは次のとおりです。

IEnumerator<byte[]> IEnumerable<byte[]>.GetEnumerator() {
    using(FileStream fs = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, _bufferSize)) {
        byte[] buffer = new byte[_bufferSize];
        int bytesRead;
        int overflowCount = 0;
        while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
            int bufferLength = bytesRead + overflowCount;
            int lastPos = 0;
            for(int i = 0; i < bufferLength; i++) {
                if(buffer[i] == 13 || buffer[i] == 10) {
                    int length = i - lastPos;
                    if(length > 0) {
                        byte[] line = new byte[length];
                        Array.Copy(buffer, lastPos, line, 0, length);
                        yield return line;
                    }
                    lastPos = i + 1;
                }
            }
            if(lastPos > 0) {
                overflowCount = bufferLength - lastPos;
                Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
            }
        }
    }
}

これはより高速なコード ブロックです (~320mb/s):

while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
    int bufferLength = bytesRead + overflowCount;
    int pos = 0;
    int lastPos = 0;
    while(pos < bufferLength && (pos = Array.IndexOf<byte>(buffer, 13, pos)) != -1) {
        int length = pos - lastPos;
        if(length > 0) {
            byte[] line = new byte[length];
            Array.Copy(buffer, lastPos, line, 0, length);
            yield return line;
        }
        if(pos < bufferLength - 1 && buffer[pos + 1] == 10)
            pos++;
        lastPos = ++pos;

    }
    if(lastPos > 0) {
        overflowCount = bufferLength - lastPos;
        Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
    }
}

(いいえ、本番環境には対応していません。特定のケースでは爆発します。私はそれらのほとんどを無視するために 128kb サイズのバッファを使用しています。)

だから私の大きな質問は...なぜ Array.IndexOf はそれほど速く動作するのですか? 本質的に同じで、配列を歩く for ループです。mscorlib コードの実行方法について何かありますか? 上記のコードを実際に IndexOf を複製するように変更し、CR だけを探してから、IndexOf を使用する場合のように LF をスキップしても役に立ちません。エラー...私はさまざまな順列を経験してきましたが、おそらく私が見逃している明らかなバグがあるほど遅いですか?

ところで、ReadLine を調べたところ、if ブロックではなく switch ブロックを使用していることに気付きました...似たようなことをすると、奇妙なことに、パフォーマンスが約 15 mb/s 向上します。それはまた別の質問です (なぜ switch は if よりも速いのですか?) しかし、私はそれを見ていたことを指摘したいと思いました。

また、VS の外部でリリース ビルドをテストしているため、デバッグは行われません。

4

2 に答える 2

2

それは良い質問です。短いバージョンでは、IndexOf が使用する IEqualityComparer の実装に要約されます。次のコードを見てみましょう。

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Program {

    static int [] buffer = new int [1024];
    const byte mark = 42;
    const int iterations = 10000;

    static void Main ()
    {
        buffer [buffer.Length -1] = mark;

        Console.WriteLine (EqualityComparer<int>.Default.GetType ());

        Console.WriteLine ("Custom:  {0}", Time (CustomIndexOf));
        Console.WriteLine ("Builtin: {0}", Time (ArrayIndexOf));
    }

    static TimeSpan Time (Action action)
    {
        var watch = new Stopwatch ();
        watch.Start ();
        for (int i = 0; i < iterations; i++)
            action ();
        watch.Stop ();
        return watch.Elapsed;
    }

    static void CustomIndexOf ()
    {
        for (int i = 0; i < buffer.Length; i++)
            if (buffer [i] == mark)
                break;
    }

    static void ArrayIndexOf ()
    {
        Array.IndexOf (buffer, mark);
    }
}

csc /optimize+でコンパイルする必要があります。

これが私が持っている結果です:

C:\Tmp>test
System.Collections.Generic.GenericEqualityComparer`1[System.Int32]
Custom:  00:00:00.0386403
Builtin: 00:00:00.0427903

ここで、配列の型と EqualityComparer の型をバイトに変更します。結果は次のとおりです。

C:\Tmp>test
System.Collections.Generic.ByteEqualityComparer
Custom:  00:00:00.0387158
Builtin: 00:00:00.0165881

ご覧のとおり、バイト配列は特殊なケースであり、おそらくバイト配列内のバイトを検索するように最適化されています。.net フレームワークを逆コンパイルできないので、ここで分析を停止しましたが、それはかなり良い手がかりだと思います。

于 2009-05-21T09:31:02.797 に答える
2

mscorlib ファイルは、インストール中に ngen 化されます。Ngen.exeユーティリティ(.NETフレームワークと一緒に提供されていると思います)を使用してファイルをngen'ingしてみてください...そしてベンチマークを確認してください。少し速くなる可能性があります。

.NET コードをネイティブに近い速度で実行するには、アプリのインストール中にコードを "Ngen" にすることを Microsoft は推奨しています...

于 2009-05-21T08:57:49.790 に答える