[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 ...
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()
và 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.
_Well_, tiêu đề sẽ tốt hơn? http://meta.stackexchange.com/questions/10647/how-do-i-write-a-good-title –
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ì? –
@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). –