2015-11-21 153 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()调用之后会发生很长时间,您的程序不可观察。