2009-05-21 12 views
4

これは私が掘り下げた個人的なプロジェクトです。基本的には、私はStreamReaderを使ってテキストファイル(20MBから約1GBまで)を解析します。パフォーマンスはかなり安定していますが、まだ...バイナリで解析すると何が起こるかを知りたいのです。誤解しないで、私は時期尚早に最適化していません。私はdefintelyちょうど "見て"目的のためにマイクロ最適化しています。System/mscorlibのコードが非常に高速なのはなぜですか?特にループのために?

私はバイト配列を使ってテキストファイルを読み込んでいます。新しいラインは、(Windows)標準のCR/LFかCRかLFかを見つけることができます。かなり面倒です。 Array.IndexOfをCRで使用し、LFをスキップすることができるようにしたいと考えていました。代わりに、IndexOfと非常によく似たコードを書いていますが、どちらかをチェックして必要に応じて配列を返すことができます。

このように、IndexOfに非常によく似たコードを使用することで、私のコードは非常に遅くなります。

  • IndexOfメソッドを使用してCRを探して::800メガバイトのファイルを使用した視点でそれを置くために〜のStreamReaderとReadLineメソッドを使用して320メガバイト/ sの
  • :〜180メガバイト/ sの
  • IndexOfメソッドを複製するforループ:〜150メガバイト/ S

は、ここでは、forループ(〜150メガバイト/秒)とのコードです:

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); 
      } 
     } 
    } 
} 

これは高速なコードブロック(〜320メートルでありますB/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を探して、LFをスキップすると、IndexOfを使用しても効果はありません。 Errr ...私は様々な並べ替えを行ってきました、そして、おそらく、私は行方不明の眩しいバグがあるでしょうか?

私はReadLineを調べ、ifブロックではなくスイッチブロックを使用していることに気付きました。私が似たようなことをすると、奇妙なことに、約15mb/sほど性能が向上します。それは別の時間のためのもう一つの質問です(なぜスイッチはif?より速いのですか?)しかし、私はそれを見たと指摘しました。

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

+0

うん、面白いです。反射鏡を使ってmscorlibの変種を見ると、そこにはどんなトリッキーなものが見られません。 – Joey

+0

実際にトリッキーを使用してしまいました。 IEqualityComparer .DefaultはByteEqualityComparerを使用して終了しました。 IndexOfの既定の実装があったため、最初に気付かなかった。 Defaultプロパティをチェックすると、シェルゲームが実行され、バイトの特殊な場合にスワップされます。 –

答えて

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 /最適化+でそれをコンパイルする必要があります。バイトにEqualityComparer配列のとの種類を変更し、今

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

、ここで私が持っている結果だ:ここで

は、私が持っている結果だ

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

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

+0

ビンゴ!それはByteEqualityComparerを使用して終了しました.Bufferはポインタを使用してBufferを使用しました。 同様のことをして、私は今〜350mb/sを打った。 私は、管理されたパフォーマンスにむしろ失望していると言わざるを得ない。私が本当にこの小さなプロジェクトのために安全ではないかどうか確かめてください。 –

2

mscorlibファイルは、インストール中にngenされます。 Ngen.exeユーティリティ(私が想定している.NETフレームワークと一緒に提供されています)を使用してファイルをngen'ingしてから、ベンチマークを確認してください。 少し速いかもしれません。良い質問です

ネイティブに近いスピードであなたの.NETコードの実行を行うために、Microsoftは、あなたが「NGEN」アプリのインストール時に、あなたのコードをお勧めします...

+2

ngenはジャストインタイムコンパイラ以外の何もしません。違いは、それは仕事を前もって行います。 (したがって、コードを2回目に実行すると同じ結果が期待されます。そのため、既にプリコンパイルされているためです) –

関連する問題