2013-04-30 4 views
18

[編集]Windows APIはBinaryWriterよりもはるかに速いようです - 私のテストは正しいですか?

@VilleKrumlindeのおかげで、誤ってコードアナライザーの警告を回避しようとしたときに誤って導入したバグを修正しました。私は誤ってファイルの長さをリセットしていた「重複した」ファイル操作を有効にしていました。これで問題は解決されました。問題なく同じストリームに対してFastWrite()を複数回呼び出すことができます。

[終了編集]


概要

私は、ディスクへの構造体の配列を書き込みする2つの異なる方法を比較するために、いくつかのタイミングテストをやっています。私は、知覚される知恵は、I/Oコストが他のものと比べて非常に高く、他のものを最適化するのにあまり時間を費やす価値がないということです。

ただし、私のタイミングテストではそうでないことが示されています。私は間違いを犯しています(これは完全に可能です)、私の最適化は本当に重要です。

歴史

は、まず、いくつかの歴史:このFastWrite()方法はもともと、従来のC++プログラムによって消費されたファイルへの書き込みの構造体をサポートするために、数年前に書かれた、と我々はまだ、この目的のためにそれを使用しています。 (対応するFastRead()メソッドもあります)主にblittable構造体の配列をファイルに書き込むことを容易にするために書かれたもので、スピードは第二の関心事でした。

私は、このような最適化が単にBinaryWriterを使用するよりもはるかに速いとは言われていないので、ついに弾丸に噛まれ、いくつかのタイミングテストを実施しました。 50倍高速BinaryWriterを使用して同等以上 - 結果は

それは私FastWrite()方法が30であること表示されます...私を驚かせています。それはばかげているように見えるので、誰かがエラーを見つけることができるかどうか確認するために私のコードをここに掲載しています。

システム仕様

  • デバッガの外部から実行のx86リリースビルドを、テストされました。
  • Windows 8、x64、16GBのメモリで動作します。
  • 通常のハードドライブ(SSDではなく)で実行します。

    SlowWrite() took 00:00:02.0747141 
    FastWrite() took 00:00:00.0318139 
    SlowWrite() took 00:00:01.9205158 
    FastWrite() took 00:00:00.0327242 
    SlowWrite() took 00:00:01.9289878 
    FastWrite() took 00:00:00.0321100 
    SlowWrite() took 00:00:01.9374454 
    FastWrite() took 00:00:00.0316074 
    

    あなたが見ることができるように、それはそうです:Visual Studioの2012による.NET 4(そう純4.5がインストールされている)

    私の結果は

結果を用いて

  • その実行時にFastWrite()が50倍高速であることを示します。

    ここに私のテストコードです。テストを実行した後、2つのファイルをバイナリ比較して、それらが実際に同一であることを確認しました。FastWrite()およびSlowWrite()が同一のファイルを生成しました)。

    は、あなたがそれを作ることができるものを参照してください。 :)

    using System; 
    using System.ComponentModel; 
    using System.Diagnostics; 
    using System.IO; 
    using System.Runtime.InteropServices; 
    using System.Text; 
    using System.Threading; 
    using Microsoft.Win32.SafeHandles; 
    
    namespace ConsoleApplication1 
    { 
        internal class Program 
        { 
    
         [StructLayout(LayoutKind.Sequential, Pack = 1)] 
         struct TestStruct 
         { 
          public byte ByteValue; 
          public short ShortValue; 
          public int IntValue; 
          public long LongValue; 
          public float FloatValue; 
          public double DoubleValue; 
         } 
    
         static void Main() 
         { 
          Directory.CreateDirectory("C:\\TEST"); 
          string filename1 = "C:\\TEST\\TEST1.BIN"; 
          string filename2 = "C:\\TEST\\TEST2.BIN"; 
    
          int count = 1000; 
          var array = new TestStruct[10000]; 
    
          for (int i = 0; i < array.Length; ++i) 
           array[i].IntValue = i; 
    
          var sw = new Stopwatch(); 
    
          for (int trial = 0; trial < 4; ++trial) 
          { 
           sw.Restart(); 
    
           using (var output = new FileStream(filename1, FileMode.Create)) 
           using (var writer = new BinaryWriter(output, Encoding.Default, true)) 
           { 
            for (int i = 0; i < count; ++i) 
            { 
             output.Position = 0; 
             SlowWrite(writer, array, 0, array.Length); 
            } 
           } 
    
           Console.WriteLine("SlowWrite() took " + sw.Elapsed); 
           sw.Restart(); 
    
           using (var output = new FileStream(filename2, FileMode.Create)) 
           { 
            for (int i = 0; i < count; ++i) 
            { 
             output.Position = 0; 
             FastWrite(output, array, 0, array.Length); 
            } 
           } 
    
           Console.WriteLine("FastWrite() took " + sw.Elapsed); 
          } 
         } 
    
         static void SlowWrite(BinaryWriter writer, TestStruct[] array, int offset, int count) 
         { 
          for (int i = offset; i < offset + count; ++i) 
          { 
           var item = array[i]; // I also tried just writing from array[i] directly with similar results. 
           writer.Write(item.ByteValue); 
           writer.Write(item.ShortValue); 
           writer.Write(item.IntValue); 
           writer.Write(item.LongValue); 
           writer.Write(item.FloatValue); 
           writer.Write(item.DoubleValue); 
          } 
         } 
    
         static void FastWrite<T>(FileStream fs, T[] array, int offset, int count) where T: struct 
         { 
          int sizeOfT = Marshal.SizeOf(typeof(T)); 
          GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); 
    
          try 
          { 
           uint bytesWritten; 
           uint bytesToWrite = (uint)(count * sizeOfT); 
    
           if 
           (
            !WriteFile 
            (
             fs.SafeFileHandle, 
             new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64() + (offset*sizeOfT)), 
             bytesToWrite, 
             out bytesWritten, 
             IntPtr.Zero 
            ) 
           ) 
           { 
            throw new IOException("Unable to write file.", new Win32Exception(Marshal.GetLastWin32Error())); 
           } 
    
           Debug.Assert(bytesWritten == bytesToWrite); 
          } 
    
          finally 
          { 
           gcHandle.Free(); 
          } 
         } 
    
         [DllImport("kernel32.dll", SetLastError=true)] 
         [return: MarshalAs(UnmanagedType.Bool)] 
    
         private static extern bool WriteFile 
         (
          SafeFileHandle hFile, 
          IntPtr   lpBuffer, 
          uint   nNumberOfBytesToWrite, 
          out uint  lpNumberOfBytesWritten, 
          IntPtr   lpOverlapped 
         ); 
        } 
    } 
    

    フォローアップ

    次のように私はまた、@ErenErsönmezによって提案されたコードをテストしている(と私はすべての3つのファイルは、テストの終わりに同一であることを確認さ):

    static void ErenWrite<T>(FileStream fs, T[] array, int offset, int count) where T : struct 
    { 
        // Note: This doesn't use 'offset' or 'count', but it could easily be changed to do so, 
        // and it doesn't change the results of this particular test program. 
    
        int size = Marshal.SizeOf(typeof(TestStruct)) * array.Length; 
        var bytes = new byte[size]; 
        GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); 
    
        try 
        { 
         var ptr = new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64()); 
         Marshal.Copy(ptr, bytes, 0, size); 
         fs.Write(bytes, 0, size); 
        } 
    
        finally 
        { 
         gcHandle.Free(); 
        } 
    } 
    

    私はそのコードのテストを追加し、同時にファイルは263K(妥当なサイズ)になるようにoutput.Position = 0;という行を削除しました。これらの変更により

    、結果は以下のとおりです。

    NOTEルックでFastWrite()時間がある遅くどのくらいのときないキープバックファイルポインタをリセットし、ゼロに!:

    SlowWrite() took 00:00:01.9929327 
    FastWrite() took 00:00:00.1152534 
    ErenWrite() took 00:00:00.2185131 
    SlowWrite() took 00:00:01.8877979 
    FastWrite() took 00:00:00.2087977 
    ErenWrite() took 00:00:00.2191266 
    SlowWrite() took 00:00:01.9279477 
    FastWrite() took 00:00:00.2096208 
    ErenWrite() took 00:00:00.2102270 
    SlowWrite() took 00:00:01.7823760 
    FastWrite() took 00:00:00.1137891 
    ErenWrite() took 00:00:00.3028128 
    

    あなたは全くのWindows APIを使用せずにマーシャリングを使用して、ほぼ同じ速度を達成できるようなので、それが見えます。唯一の欠点は、Erenのメソッドが構造体の配列全体をコピーしなければならないことです。メモリが限られていると問題になる可能性があります。

  • +0

    _Well_、タイトルはよかったですか?最初のループは 'FastWrite'及び第二のループは' SlowWrite'呼び出しを呼び出す場合http://meta.stackexchange.com/questions/10647/how-do-i-write-a-good-title –

    +0

    結果は何ですか? –

    +0

    @DanielHilgarthそれは違いはありませんが、(私はそのような影響を最小限に抑えるために外側のループを持っているので)期待していませんでした。 –

    答えて

    18

    私は違いがBinaryWriterに関係しているとは思いません。私はSlowWrite(10000 * 6)で複数のファイルIOを実行しているのに対して、FastWriteでは単一のIOであることが原因だと思います。 FastWriteには、ファイルに書き込む準備ができている1バイトのバイトがあるという利点があります。一方、SlowWriteでは、構造体を1バイトずつバイト配列に変換するというヒットを得ています。この理論をテストする

    は、私はすべての構造体の大きなバイト配列を事前に構築し、ほとんどの方法を書いて、その後、SlowWriteでこのバイト配列を使用:

    static byte[] bytes; 
    static void Prep(TestStruct[] array) 
    { 
        int size = Marshal.SizeOf(typeof(TestStruct)) * array.Length; 
        bytes = new byte[size]; 
        GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); 
        var ptr = gcHandle.AddrOfPinnedObject(); 
        Marshal.Copy(ptr, bytes, 0, size); 
        gcHandle.Free(); 
    } 
    
    static void SlowWrite(BinaryWriter writer) 
    { 
        writer.Write(bytes); 
    } 
    

    結果:

    SlowWrite() took 00:00:00.0360392 
    FastWrite() took 00:00:00.0385015 
    SlowWrite() took 00:00:00.0358703 
    FastWrite() took 00:00:00.0381371 
    SlowWrite() took 00:00:00.0373875 
    FastWrite() took 00:00:00.0367692 
    SlowWrite() took 00:00:00.0348295 
    FastWrite() took 00:00:00.0373931 
    

    お知らせSlowWriteは今FastWriteに非常に匹敵し実行していること、そして私は、これはパフォーマンスの違いは、実際のIO性能に起因するが、バイナリ変換プロセスに複数の関連ではないことを示していると思います。

    +0

    はい、私はあなたの分析に同意します。明らかに、バイト配列を書くだけであれば、他のメソッドが持つデータを固定するオーバーヘッドはありません。とにかく、それはなぜそれがずっと速いのかを確かに説明します。 –

    +1

    SerializeMessageとは何ですか? –

    +0

    @SimonMourier良い点が追加されました。それは私が[ここ](http://wingerlang.blogspot.com/2011/11/c-struct-to-byte-array-and-back.html)で見つけた小さなヘルパーメソッドです。 –

    関連する問題