我試圖使用內存映射文件來編寫具有較高IO要求的應用程序。在這個應用程序中,我收到的數據以比磁盤能夠支持的速度更快的速度接收。爲了避免在我的應用程序中使用緩衝邏輯,我想過使用內存映射文件。使用這種文件,我只需將其寫入映射到文件的內存(比磁盤可以支持的更快),操作系統最終會將此數據刷新到磁盤。操作系統因此爲我做緩衝。寫入內存映射文件比非內存映射文件慢
實驗後,我發現內存映射文件使寫入內存更快,但刷新磁盤比使用普通文件更慢。這是什麼使我得出這個結論。下面是一段代碼,只是將盡可能快,因爲它可以以非內存映射文件:
private static void WriteNonMemoryMappedFile(long fileSize, byte[] bufferToWrite)
{
Console.WriteLine(" ==> Non memory mapped file");
string normalFileName = Path.Combine(Path.GetTempPath(), "MemoryMappedFileWriteTest-NonMmf.bin");
if (File.Exists(normalFileName))
{
File.Delete(normalFileName);
}
var stopWatch = Stopwatch.StartNew();
using (var file = File.OpenWrite(normalFileName))
{
var numberOfPages = fileSize/bufferToWrite.Length;
for (int page = 0; page < numberOfPages; page++)
{
file.Write(bufferToWrite, 0, bufferToWrite.Length);
}
}
Console.WriteLine("Non-memory mapped file is now closed after {0} seconds ({1} MB/s)", stopWatch.Elapsed.TotalSeconds, GetSpeed(fileSize, stopWatch));
}
該代碼產生這樣的:
==> Non memory mapped file
Non-memory mapped file is now closed after 10.5918587 seconds (966.687541390441 MB/s)
正如你看到的,我的磁盤蠻快。這將是我對內存映射文件的基準。
現在我試着寫相同的數據使用不安全的代碼內存映射文件(因爲這是我打算在我的應用做):
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
public static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);
private static unsafe void WriteMemoryMappedFileWithUnsafeCode(long fileSize, byte[] bufferToWrite)
{
Console.WriteLine(" ==> Memory mapped file with unsafe code");
string fileName = Path.Combine(Path.GetTempPath(), "MemoryMappedFileWriteTest-MmfUnsafeCode.bin");
if (File.Exists(fileName))
{
File.Delete(fileName);
}
string mapName = Guid.NewGuid().ToString();
var stopWatch = Stopwatch.StartNew();
using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(fileName, FileMode.Create, mapName, fileSize, MemoryMappedFileAccess.ReadWrite))
using (var view = memoryMappedFile.CreateViewAccessor(0, fileSize, MemoryMappedFileAccess.Write))
{
unsafe
{
fixed (byte* pageToWritePointer = bufferToWrite)
{
byte* pointer = null;
try
{
view.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
var writePointer = pointer;
var numberOfPages = fileSize/bufferToWrite.Length;
for (int page = 0; page < numberOfPages; page++)
{
memcpy((IntPtr) writePointer, (IntPtr) pageToWritePointer, (UIntPtr) bufferToWrite.Length);
writePointer += bufferToWrite.Length;
}
}
finally
{
if (pointer != null)
view.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}
Console.WriteLine("All bytes written in MMF after {0} seconds ({1} MB/s). Will now close MMF. This may be long since some content may not have been flushed to disk yet.", stopWatch.Elapsed.TotalSeconds, GetSpeed(fileSize, stopWatch));
}
Console.WriteLine("File is now closed after {0} seconds ({1} MB/s)", stopWatch.Elapsed.TotalSeconds, GetSpeed(fileSize, stopWatch));
}
然後我得到這個:
==> Memory mapped file with unsafe code
All bytes written in MMF after 6.5442406 seconds (1564.73302033172 MB/s). Will now close MMF. This may be long since some content may not have been flushed to disk yet.
File is now closed after 18.8873186 seconds (542.162704287661 MB/s)
正如你所看到的,這太慢了。它寫入大約56%的非內存映射文件。
然後我試了另一件事。我試圖用ViewStreamAccessor代替不安全代碼:
private static unsafe void WriteMemoryMappedFileWithViewStream(long fileSize, byte[] bufferToWrite)
{
Console.WriteLine(" ==> Memory mapped file with view stream");
string fileName = Path.Combine(Path.GetTempPath(), "MemoryMappedFileWriteTest-MmfViewStream.bin");
if (File.Exists(fileName))
{
File.Delete(fileName);
}
string mapName = Guid.NewGuid().ToString();
var stopWatch = Stopwatch.StartNew();
using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(fileName, FileMode.Create, mapName, fileSize, MemoryMappedFileAccess.ReadWrite))
using (var viewStream = memoryMappedFile.CreateViewStream(0, fileSize, MemoryMappedFileAccess.Write))
{
var numberOfPages = fileSize/bufferToWrite.Length;
for (int page = 0; page < numberOfPages; page++)
{
viewStream.Write(bufferToWrite, 0, bufferToWrite.Length);
}
Console.WriteLine("All bytes written in MMF after {0} seconds ({1} MB/s). Will now close MMF. This may be long since some content may not have been flushed to disk yet.", stopWatch.Elapsed.TotalSeconds, GetSpeed(fileSize, stopWatch));
}
Console.WriteLine("File is now closed after {0} seconds ({1} MB/s)", stopWatch.Elapsed.TotalSeconds, GetSpeed(fileSize, stopWatch));
}
然後我得到這樣的:
==> Memory mapped file with view stream
All bytes written in MMF after 4.6713875 seconds (2192.06548076352 MB/s). Will now close MMF. This may be long since some content may not have been flushed to disk yet.
File is now closed after 16.8921666 seconds (606.198141569359 MB/s)
再次,這是不是與非內存映射文件顯著慢。
那麼,有沒有人知道如何使內存映射文件與寫入時的非內存映射文件一樣快?
順便說一句,這裏是我的測試程序的其餘部分:
static void Main(string[] args)
{
var bufferToWrite = Enumerable.Range(0, Environment.SystemPageSize * 256).Select(i => (byte)i).ToArray();
long fileSize = 10 * 1024 * 1024 * 1024L; // 2 GB
WriteNonMemoryMappedFile(fileSize, bufferToWrite);
WriteMemoryMappedFileWithUnsafeCode(fileSize, bufferToWrite);
WriteMemoryMappedFileWithViewStream(fileSize, bufferToWrite);
}
private static double GetSpeed(long fileSize, Stopwatch stopwatch)
{
var mb = fileSize/1024.0/1024.0;
var mbPerSec = mb/stopwatch.Elapsed.TotalSeconds;
return mbPerSec;
}
編輯1:
至於建議的USR,我試圖用SequenctialScan選項。不幸的是,它沒有任何影響。這裏是我做了改變:
using (var file = new FileStream(fileName, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.SequentialScan))
using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(file, mapName, fileSize, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, leaveOpen: false))