2015-11-21 192 views
4

我試圖使用內存映射文件來編寫具有較高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)) 

回答

3

SDK documentation

,直到他們的股數達到零,或者換句話說,直到他們在未映射視圖

修改頁面不會被寫入到磁盤從共享頁面的所有進程的工作集中取消映射或修剪。即使這樣,修改後的頁面也會「懶洋洋地」寫入磁盤;也就是說,修改可能會緩存在內存中,並在稍後寫入磁盤。爲了儘量減少電源故障或系統崩潰時數據丟失的風險,應用程序應使用FlushViewOfFile函數顯式刷新修改後的頁面。

.NET程序員認真地說了最後一句,你調用的MemoryMappedViewStream.Dispose() method確實調用了FlushViewOfFile()。這需要時間,您在個人資料結果中看到了這一點。在技​​術上可以繞過這個調用,不要調用Dispose()並讓終結器關閉視圖句柄。

FileStream不會執行文件(FlushFileBuffers)的等效功能,因此您可以充分利用從文件系統緩存到磁盤的惰性寫入。在Dispose()調用之後會發生很長時間,您的程序不可觀察。