Từ thông tin về đảm bảo dữ liệu nằm trên đĩa (http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/), ngay cả trong trường hợp ví dụ: cúp điện, có vẻ như trên nền tảng Windows, bạn cần phải dựa vào phiên bản "fsync" FlushFileBuffers
để đảm bảo tốt nhất các bộ đệm thực sự bị xóa khỏi bộ nhớ thiết bị đĩa trên chính phương tiện lưu trữ. Sự kết hợp của FILE_FLAG_NO_BUFFERING
với FILE_FLAG_WRITE_THROUGH
không đảm bảo xóa bộ nhớ cache của thiết bị, nhưng chỉ có tác dụng trên bộ nhớ cache của hệ thống tệp, nếu thông tin này là chính xác.Hiệu suất Windows fsync (FlushFileBuffers) với tệp lớn
Với thực tế là tôi sẽ làm việc với các tệp khá lớn, cần được cập nhật "giao dịch", điều này có nghĩa là thực hiện "fsync" ở cuối giao dịch cam kết. Vì vậy, tôi đã tạo một ứng dụng nhỏ để kiểm tra hiệu suất khi làm như vậy. Về cơ bản nó thực hiện việc ghi tuần tự của một loạt 8 byte bộ nhớ có kích thước ngẫu nhiên bằng cách sử dụng 8 lần viết và sau đó xóa đi. Lô được lặp đi lặp lại trong một vòng lặp, và sau mỗi rất nhiều trang văn bản ghi lại hiệu suất. Ngoài ra nó có hai tùy chọn cấu hình: để fsync trên một tuôn ra và cho dù để viết một byte đến vị trí cuối cùng của tập tin, trước khi bắt đầu viết trang.
// Code updated to reflect new results as discussed in answer below.
// 26/Aug/2013: Code updated again to reflect results as discussed in follow up question.
// 28/Aug/2012: Increased file stream buffer to ensure 8 page flushes.
class Program
{
static void Main(string[] args)
{
BenchSequentialWrites(reuseExistingFile:false);
}
public static void BenchSequentialWrites(bool reuseExistingFile = false)
{
Tuple<string, bool, bool, bool, bool>[] scenarios = new Tuple<string, bool, bool, bool, bool>[]
{ // output csv, fsync?, fill end?, write through?, mem map?
Tuple.Create("timing FS-E-B-F.csv", true, false, false, false),
Tuple.Create("timing NS-E-B-F.csv", false, false, false, false),
Tuple.Create("timing FS-LB-B-F.csv", true, true, false, false),
Tuple.Create("timing NS-LB-B-F.csv", false, true, false, false),
Tuple.Create("timing FS-E-WT-F.csv", true, false, true, false),
Tuple.Create("timing NS-E-WT-F.csv", false, false, true, false),
Tuple.Create("timing FS-LB-WT-F.csv", true, true, true, false),
Tuple.Create("timing NS-LB-WT-F.csv", false, true, true, false),
Tuple.Create("timing FS-E-B-MM.csv", true, false, false, true),
Tuple.Create("timing NS-E-B-MM.csv", false, false, false, true),
Tuple.Create("timing FS-LB-B-MM.csv", true, true, false, true),
Tuple.Create("timing NS-LB-B-MM.csv", false, true, false, true),
Tuple.Create("timing FS-E-WT-MM.csv", true, false, true, true),
Tuple.Create("timing NS-E-WT-MM.csv", false, false, true, true),
Tuple.Create("timing FS-LB-WT-MM.csv", true, true, true, true),
Tuple.Create("timing NS-LB-WT-MM.csv", false, true, true, true),
};
foreach (var scenario in scenarios)
{
Console.WriteLine("{0,-12} {1,-16} {2,-16} {3,-16} {4:F2}", "Total pages", "Interval pages", "Total time", "Interval time", "MB/s");
CollectGarbage();
var timingResults = SequentialWriteTest("test.data", !reuseExistingFile, fillEnd: scenario.Item3, nPages: 200 * 1000, fSync: scenario.Item2, writeThrough: scenario.Item4, writeToMemMap: scenario.Item5);
using (var report = File.CreateText(scenario.Item1))
{
report.WriteLine("Total pages,Interval pages,Total bytes,Interval bytes,Total time,Interval time,MB/s");
foreach (var entry in timingResults)
{
Console.WriteLine("{0,-12} {1,-16} {2,-16} {3,-16} {4:F2}", entry.Item1, entry.Item2, entry.Item5, entry.Item6, entry.Item7);
report.WriteLine("{0},{1},{2},{3},{4},{5},{6}", entry.Item1, entry.Item2, entry.Item3, entry.Item4, entry.Item5.TotalSeconds, entry.Item6.TotalSeconds, entry.Item7);
}
}
}
}
public unsafe static IEnumerable<Tuple<long, long, long, long, TimeSpan, TimeSpan, double>> SequentialWriteTest(
string fileName,
bool createNewFile,
bool fillEnd,
long nPages,
bool fSync = true,
bool writeThrough = false,
bool writeToMemMap = false,
long pageSize = 4096)
{
// create or open file and if requested fill in its last byte.
var fileMode = createNewFile ? FileMode.Create : FileMode.OpenOrCreate;
using (var tmpFile = new FileStream(fileName, fileMode, FileAccess.ReadWrite, FileShare.ReadWrite, (int)pageSize))
{
Console.WriteLine("Opening temp file with mode {0}{1}", fileMode, fillEnd ? " and writing last byte." : ".");
tmpFile.SetLength(nPages * pageSize);
if (fillEnd)
{
tmpFile.Position = tmpFile.Length - 1;
tmpFile.WriteByte(1);
tmpFile.Position = 0;
tmpFile.Flush(true);
}
}
// Make sure any flushing/activity has completed
System.Threading.Thread.Sleep(TimeSpan.FromMinutes(1));
System.Threading.Thread.SpinWait(50); // warm up.
var buf = new byte[pageSize];
new Random().NextBytes(buf);
var ms = new System.IO.MemoryStream(buf);
var stopwatch = new System.Diagnostics.Stopwatch();
var timings = new List<Tuple<long, long, long, long, TimeSpan, TimeSpan, double>>();
var pageTimingInterval = 8 * 2000;
var prevPages = 0L;
var prevElapsed = TimeSpan.FromMilliseconds(0);
// Open file
const FileOptions NoBuffering = ((FileOptions)0x20000000);
var options = writeThrough ? (FileOptions.WriteThrough | NoBuffering) : FileOptions.None;
using (var file = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, (int)(16 *pageSize), options))
{
stopwatch.Start();
if (writeToMemMap)
{
// write pages through memory map.
using (var mmf = MemoryMappedFile.CreateFromFile(file, Guid.NewGuid().ToString(), file.Length, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, true))
using (var accessor = mmf.CreateViewAccessor(0, file.Length, MemoryMappedFileAccess.ReadWrite))
{
byte* base_ptr = null;
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref base_ptr);
var offset = 0L;
for (long i = 0; i < nPages/8; i++)
{
using (var memStream = new UnmanagedMemoryStream(base_ptr + offset, 8 * pageSize, 8 * pageSize, FileAccess.ReadWrite))
{
for (int j = 0; j < 8; j++)
{
ms.CopyTo(memStream);
ms.Position = 0;
}
}
FlushViewOfFile((IntPtr)(base_ptr + offset), (int)(8 * pageSize));
offset += 8 * pageSize;
if (fSync)
FlushFileBuffers(file.SafeFileHandle);
if (((i + 1) * 8) % pageTimingInterval == 0)
timings.Add(Report(stopwatch.Elapsed, ref prevElapsed, (i + 1) * 8, ref prevPages, pageSize));
}
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
else
{
for (long i = 0; i < nPages/8; i++)
{
for (int j = 0; j < 8; j++)
{
ms.CopyTo(file);
ms.Position = 0;
}
file.Flush(fSync);
if (((i + 1) * 8) % pageTimingInterval == 0)
timings.Add(Report(stopwatch.Elapsed, ref prevElapsed, (i + 1) * 8, ref prevPages, pageSize));
}
}
}
timings.Add(Report(stopwatch.Elapsed, ref prevElapsed, nPages, ref prevPages, pageSize));
return timings;
}
private static Tuple<long, long, long, long, TimeSpan, TimeSpan, double> Report(TimeSpan elapsed, ref TimeSpan prevElapsed, long curPages, ref long prevPages, long pageSize)
{
var intervalPages = curPages - prevPages;
var intervalElapsed = elapsed - prevElapsed;
var intervalPageSize = intervalPages * pageSize;
var mbps = (intervalPageSize/(1024.0 * 1024.0))/intervalElapsed.TotalSeconds;
prevElapsed = elapsed;
prevPages = curPages;
return Tuple.Create(curPages, intervalPages, curPages * pageSize, intervalPageSize, elapsed, intervalElapsed, mbps);
}
private static void CollectGarbage()
{
GC.Collect();
GC.WaitForPendingFinalizers();
System.Threading.Thread.Sleep(200);
GC.Collect();
GC.WaitForPendingFinalizers();
System.Threading.Thread.SpinWait(10);
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FlushViewOfFile(
IntPtr lpBaseAddress, int dwNumBytesToFlush);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool FlushFileBuffers(SafeFileHandle hFile);
}
Kết quả hiệu suất tôi nhận được (64 bit Win 7, đĩa trục chậm) không đáng khích lệ. Có vẻ như hiệu suất "fsync" phụ thuộc rất nhiều vào kích thước của tệp đang được xóa, sao cho điều này chiếm ưu thế thời gian và không phải số lượng dữ liệu "bẩn" được xóa. Biểu đồ bên dưới hiển thị kết quả cho 4 tùy chọn cài đặt khác nhau của ứng dụng chuẩn nhỏ.
Như bạn thấy, hiệu suất của "fsync" theo cấp số nhân giảm khi các tập tin phát triển (cho đến khi tại một vài GB nó thực sự kim để ngăn chặn). Ngoài ra, bản thân đĩa dường như không hoạt động nhiều (nghĩa là màn hình tài nguyên hiển thị thời gian hoạt động của nó chỉ khoảng vài phần trăm và hàng đợi đĩa của nó hầu như trống trong hầu hết thời gian). Tôi đã rõ ràng dự kiến hiệu suất "fsync" sẽ khá tồi tệ hơn một chút so với việc thực hiện các bước đệm bình thường, nhưng tôi đã mong đợi nó ít nhiều liên tục và độc lập với kích thước tệp. Như thế này, nó có vẻ gợi ý rằng nó không thể sử dụng kết hợp với một tập tin lớn duy nhất.
Có ai có lời giải thích, trải nghiệm khác hoặc giải pháp khác cho phép đảm bảo dữ liệu trên đĩa và có hiệu suất dự đoán nhiều hoặc ít liên tục không?
CẬP NHẬT Xem thông tin mới trong câu trả lời bên dưới.
Từ những gì tôi hiểu bài viết là chính xác. Cũng xác nhận trong ví dụ: tại đây: http://support.microsoft.com/kb/332023 (xem phần "Thông tin thêm"). 'FlushFileBuffers' chuyển thành lệnh' SYNCHRONIZE CACHE' cho các thiết bị SCSI và lệnh 'FLUSH CACHE' cho các thiết bị IDE/ATAPI. 'Write Through' được phát hành dưới dạng' ForceUnitAccess' mà dường như thường không được các thiết bị IDE/ITAPI vinh danh. Tôi tin rằng đây cũng là những gì được đề cập trong bài viết được tham chiếu ftp://ftp.research.microsoft.com/pub/tr/TR-2008-36.pdf. – Alex
@alex những bài viết này đang nói về một bộ nhớ cache bên trong đĩa/bộ điều khiển, không phải là cấp độ hệ điều hành. Khi sử dụng writethrough, nó nên đã làm cho nó thành 'trong bộ nhớ cache đĩa', nhưng nó không thực sự 'trên đĩa'. Đối với các thử nghiệm như Q của bạn, bộ nhớ cache này có thể giải thích sự suy giảm khi lượng IO của bạn tăng lên. Bộ nhớ đệm này là một vấn đề lớn đối với cơ sở dữ liệu khi các bộ nhớ cache trong ổ đĩa bị lỗi trong điều kiện powerfail trước khi được ghi vào một sector, xem http://support.microsoft.com/kb/234656 – rlb
Tôi không thể tìm thấy bất kỳ thứ gì trong đó giấy nói bất cứ điều gì về API Win32. Tuy nhiên, bài viết KB chắc chắn không! –