2013-04-30 22 views
18

[EDIT]Windows API, BinaryWriter'den çok daha hızlı görünüyor - testim doğru mu?

@VilleKrumlinde Teşekkürler Bir Kod Analizi uyarısından kaçınmaya çalışırken yanlışlıkla daha önce tanıttığım bir hatayı düzelttim. Yanlışlıkla dosya uzunluğunu sıfırlayan "örtüşen" dosya işlemeyi açıyordum. Bu şimdi düzeltildi ve sorun olmadan aynı akış için birden çok kez FastWrite()'u arayabilirsiniz.

[Bitiş Düzenle]


Bakış

Ben diske yapılar yazma Dizilerin iki farklı şekilde karşılaştırmak için bazı zamanlama testleri yapıyorum. Algılanan bilgeliğin, G/Ç maliyetlerinin diğer şeylere kıyasla çok yüksek olduğuna ve diğer şeyleri optimize etmeye çok fazla zaman harcamaya değmeyeceğine inanıyorum. Ancak, zamanlama testlerim aksini gösteriyor gibi görünüyor. Ya bir hata yapıyorum (ki bu tamamen mümkündür) ya da optimizasyonum gerçekten çok önemli.

İlk bazı tarih Tarih: Bu FastWrite() yöntem başlangıçta yazılmıştır yıl önce eski C++ program tarafından tüketildiği bir dosyaya yazma yapılar desteklemek ve biz hala bu amaçla bunu kullanıyor. (Ayrıca bir FastRead() yöntemine karşılık gelir.) Öncelikle bir dosyaya blittable struct dizileri yazmayı kolaylaştırmak için yazılmıştı ve hızı ikincil bir endişeydi.

Bu gibi optimizasyonların sadece BinaryWriter kullanmasından çok daha hızlı olmadığı bir kişiden fazla kişi tarafından söylendi, bu yüzden sonunda mermiyi ısırdım ve bazı zamanlama testleri yaptım. 50 kat daha hızlı BinaryWriter kullanarak eşdeğer daha - sonuçlar

Bu benim FastWrite() yöntem 30 olduğunu görünür ... beni şaşırttı var. Bu çok saçma görünüyor, bu yüzden ben kimsenin hataları bulabilmesini görmek için kodumu buraya gönderiyorum.

Sistem Özellikleri ayıklayıcıya DIŞINDA çalıştırılan bir x86 AÇIKLAMASI yapı, test

  • .
  • Windows 8, x64, 16 GB bellekte çalışıyor.
  • Normal bir sabit sürücüde (SSD'de değil) çalıştırın.

Sonuçları

Benim sonuçları (şimdiye .Net 4.5 yüklü) Visual Studio 2012 ile .Net 4 Kullanılması

  • şunlardır:

    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 
    

    Gördüğünüz gibi, görünüyor Bu çalışmada FastWrite()'un 50 kat daha hızlı olduğunu göstermek için.

    İşte test kodum. Testi yürüttükten sonra, gerçekten aynı olduklarını doğrulamak için iki dosyanın ikili bir karşılaştırmasını yaptım (örn.FastWrite() ve SlowWrite() aynı dosyaları üretti).

    Ne yapabileceğinizi görün. :) şöyle

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

    Takibi

    Ben de @ ErenErsönmez önerdiği kodunu test ettik (ve her üç dosyanın testin sonunda özdeş olduğu doğrulandıktan):

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

    Bu kod için bir sınama ekledim ve aynı zamanda output.Position = 0; satırlarını kaldırdım, böylece dosyalar artık 263K'ye çıkıyor (bu da makul boyutta). Bu değişikliklerle

    sonuçlar şöyle:

    NOT Bak en FastWrite() zamanlardır yavaş ne kadar zaman yapamaz tutmak geri dosya işaretçisi sıfırlama sıfır !:

    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 
    

    Bu nedenle, Windows API'sini kullanmak zorunda kalmadan, olmadan, Marshaling 'u kullanarak neredeyse aynı hıza ulaşabilirsiniz. Tek dezavantajı, Eren'in yönteminin tüm yapı dizisinin bir kopyasını yapmak zorunda olmasıdır, bu da bellek sınırlıysa bir sorun olabilir.

  • +0

    _Well_, başlık daha iyi olurdu? http://meta.stackexchange.com/questions/10647/how-do-i-write-a-good-title –

    +0

    İlk döngü "FastWrite" ve ikinci döngü "SlowWrite" olarak adlandırılırsa sonuçlar nedir? –

    +0

    @DanielHilgarth Fark etmez, ama bunu beklemezdim (çünkü bu etkiyi en aza indirgemek için dış çevrime sahibim). –

    cevap

    18

    Farkın BinaryWriter ile ilgili olduğunu sanmıyorum. Sanırım, SlowWrite (10000 * 6) 'da birden çok dosya IO'su yaptığınız için FastWrite'da tek bir IO. FastWrite'ınız, dosyaya yazmaya hazır tek bir baytlık bayt sahip olma avantajına sahiptir. Öte yandan, yapıları SlowWrite'da tek tek byte dizilerine çevirme isabeti alıyorsunuz.

    bu teoriyi test etmek için, ben önceden inşa biraz yöntemi tüm yapılar büyük bir bayt dizisi yazdı ve sonra SlowWrite bu bayt dizisi kullandı:

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

    Sonuçlar:

    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 şimdi FastWrite ile çok benzer bir performans sergilediğine dikkat edin ve bence bu, performans farkının gerçek IO performansından kaynaklanmadığını, ancak ikili dönüştürme işlemiyle daha fazla ilişkili olduğunu gösteriyor.

    +0

    Evet, analizinize katılıyorum. Açıkçası, bir bayt dizisi yazarsanız, diğer yöntemin sahip olduğu verileri sabitlemenin hiçbir yükü yoktur. Her neyse, kesinlikle neden bu kadar hızlı olduğunu açıklıyor. –

    +1

    SerializeMessage nedir? –

    +0

    @SimonMourier iyi nokta eklendi. Bu (burada) bulduğum küçük bir yardımcı yöntem (http://wingerlang.blogspot.com/2011/11/c-struct-to-byte-array-and-back.html). –

    İlgili konular