2013-04-30 16 views
18

[EDIT]Windows API có vẻ nhanh hơn BinaryWriter - bài kiểm tra của tôi có đúng không?

Nhờ @VilleKrumlinde Tôi đã khắc phục lỗi mà tôi đã vô tình giới thiệu trước đó khi cố gắng tránh cảnh báo Phân tích mã. Tôi vô tình bật tính năng xử lý tệp "chồng chéo", điều này giúp giữ lại độ dài tệp. Hiện đã được khắc phục và bạn có thể gọi FastWrite() nhiều lần cho cùng một luồng mà không gặp sự cố nào.

[End Chỉnh sửa]


Tổng quan

tôi đang làm một số xét nghiệm thời gian để so sánh hai cách khác nhau để viết mảng của cấu trúc vào đĩa. Tôi tin rằng sự khôn ngoan nhận thức là chi phí I/O quá cao so với những thứ khác mà nó không đáng để dành quá nhiều thời gian để tối ưu hóa những thứ khác.

Tuy nhiên, thử nghiệm tính thời gian của tôi dường như chỉ ra khác. Hoặc là tôi đang phạm sai lầm (điều này hoàn toàn có thể), hoặc tối ưu hóa của tôi thực sự là khá đáng kể.

Lịch sử

Đầu tiên một số lịch sử: năm phương pháp FastWrite() này ban đầu được viết trước để hỗ trợ cấu trúc văn bản cho một tập tin đó đã được tiêu thụ bởi một chương trình di sản C++, và chúng tôi vẫn đang sử dụng nó cho mục đích này. (Cũng có phương thức FastRead() tương ứng.) Nó được viết chủ yếu để làm cho nó dễ dàng hơn trong việc viết các mảng của các cấu trúc blittable vào một tập tin, và tốc độ của nó là một mối quan tâm thứ cấp.

Tôi đã được nhiều người biết rằng việc tối ưu hóa này không thực sự nhanh hơn nhiều so với chỉ sử dụng BinaryWriter, vì vậy cuối cùng tôi đã cắn viên đạn và thực hiện một số thử nghiệm định thời gian. Các kết quả đã làm tôi ngạc nhiên ...

xuất hiện mà phương pháp FastWrite() của tôi nhanh hơn 30 - 50 lần so với tương đương sử dụng BinaryWriter. Điều đó có vẻ vô lý, vì vậy tôi đăng mã của tôi ở đây để xem có ai có thể tìm thấy lỗi không.

Hệ thống kỹ thuật

  • Tested một build x86 CHÍ, chạy từ bên ngoài debugger.
  • Chạy trên bộ nhớ Windows 8, x64, 16 GB.
  • Chạy trên ổ đĩa cứng thông thường (không phải ổ SSD).
  • Sử dụng Net 4 với Visual Studio 2012 (rất Net 4.5 được cài đặt)

Kết quả

kết quả của tôi là:

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 

Như bạn có thể thấy, điều đó dường như để hiển thị rằng FastWrite() nhanh hơn 50 lần khi chạy.

Đây là mã thử nghiệm của tôi. Sau khi chạy thử nghiệm, tôi đã so sánh nhị phân hai tệp để xác minh rằng chúng thực sự giống hệt nhau (tức làFastWrite()SlowWrite() sản xuất các tệp giống hệt nhau).

Xem những gì bạn có thể tạo ra. :)

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

Follow Up

Tôi cũng đã thử nghiệm mã bởi @ ErenErsönmez đề xuất như sau (và tôi xác nhận rằng tất cả ba tác phẩm giống hệt nhau ở phần cuối của bài kiểm tra):

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

Tôi đã thêm thử nghiệm cho mã đó và đồng thời xóa các dòng output.Position = 0; để các tệp giờ đây tăng lên 263K (kích thước hợp lý).

Với những thay đổi đó, kết quả là:

LƯU Ý Nhìn vào bao nhiêu càng chậm FastWrite() thời gian được tính khi bạn không giữ đặt con trỏ tập tin trở lại bằng không !:

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 

Vì vậy, có vẻ như bạn có thể đạt được tốc độ gần như giống nhau bằng cách sử dụng Marshaling mà không cần phải sử dụng API Windows. Hạn chế duy nhất là phương pháp của Eren phải tạo một bản sao của toàn bộ mảng cấu trúc, có thể là một vấn đề nếu bộ nhớ bị giới hạn.

+0

_Well_, tiêu đề sẽ tốt hơn? http://meta.stackexchange.com/questions/10647/how-do-i-write-a-good-title –

+0

Các kết quả nếu vòng lặp đầu tiên gọi là 'FastWrite' và vòng lặp thứ hai gọi là 'SlowWrite' là gì? –

+0

@DanielHilgarth Nó không tạo ra sự khác biệt, nhưng tôi đã không mong đợi nó (vì tôi có vòng lặp ngoài để cố gắng giảm thiểu các hiệu ứng như vậy). –

Trả lời

18

Tôi không nghĩ rằng sự khác biệt phải làm với BinaryWriter. Tôi nghĩ rằng đó là do thực tế là bạn đang làm nhiều tập tin IOs trong SlowWrite (10000 * 6) so với một IO đơn trong FastWrite. FastWrite của bạn có lợi thế là có một blob các byte sẵn sàng để ghi vào tệp. Mặt khác, bạn đang thực hiện cú đánh chuyển đổi cấu trúc thành mảng byte từng cái một trong SlowWrite.

Để kiểm tra giả thuyết này, tôi đã viết một phương pháp nhỏ mà pre-xây dựng một mảng byte lớn của tất cả các cấu trúc, và sau đó sử dụng mảng byte này trong 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); 
} 

Kết quả:

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 

Lưu ý rằng SlowWrite bây giờ thực hiện rất so sánh với FastWrite và tôi nghĩ điều này cho thấy sự khác biệt hiệu suất không phải do hiệu suất IO thực tế mà có liên quan nhiều hơn đến quá trình chuyển đổi nhị phân.

+0

Có, tôi đồng ý với phân tích của bạn. Rõ ràng, nếu bạn chỉ viết một mảng các byte, nó không có chi phí của việc ghim dữ liệu mà phương thức kia có. Dù sao, nó chắc chắn giải thích tại sao nó nhanh hơn rất nhiều. –

+1

SerializeMessage là gì? –

+0

@SimonMourier điểm tốt, được thêm vào. đó là một phương pháp trợ giúp nhỏ tôi tìm thấy [ở đây] (http://wingerlang.blogspot.com/2011/11/c-struct-to-byte-array-and-back.html). –