如何动态扩展内存映射文件

我已经使用C#来解决后续需求。 – 创建一个可以快速收集大量数据的应用程序 – 您必须能够分析收到的数据,同时还要收到更多数据。 – 使用尽可能小的CPU和光盘

我对算法的想法是……

SIZE = 10MB Create a mmf with the size of SIZE On data recived: if data can't fit mmf: increase mmf.size by SIZE write the data to mmf 

– >当使用前一个“房间/空间”时,光盘上的大小增加了10MB的块。

如何在C#中“通过SIZE增加mmf.size”? 我已经找到了很多关于创建mmfs和视图的简单示例,但是我看到的唯一的地方( 链接 )实际上增加了mmfs区域的代码使用了无法编译的代码。 任何帮助都会受到很大的关注。

编辑这会导致exception:

 private void IncreaseFileSize() { int theNewMax = this.currentMax + INCREMENT_SIZE; this.currentMax = theNewMax; this.mmf.Dispose(); this.mmf = MemoryMappedFile.CreateFromFile(this.FileName, FileMode.Create, "MyMMF", theNewMax); this.view = mmf.CreateViewAccessor(0, theNewMax); } 

抛出此exception:进程无法访问文件’C:\ Users \ moberg \ Documents \ data.bin’,因为它正由另一个进程使用。

将文件映射到内存后,无法增加其大小。 这是内存映射文件的已知限制。

…你必须计算或估计完成文件的大小,因为文件映射对象的大小是静态的; 一旦创建,它们的大小不能增加或减少。

一种策略是使用存储在给定大小的非持久内存映射文件中的块,比如1GB或2GB。 您可以通过自己设计的顶级ViewAccessor来管理这些(可能需要从MemoryMappedViewAccessor所需方法的基本function)。

编辑:或者您可以创建一个您希望使用的最大大小的非持久内存映射文件(比如启动8GB,在启动应用程序时使用参数调整它)并检索每个逻辑块的MemoryMappedViewAccessor 。 在请求每个视图之前,非持久文件不会使用物理资源。

好吧, 你可以!!

这是我对可增长内存映射文件的实现:

 using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.IO.MemoryMappedFiles; namespace MmbpTree { public unsafe sealed class GrowableMemoryMappedFile : IDisposable { private const int AllocationGranularity = 64 * 1024; private class MemoryMappedArea { public MemoryMappedFile Mmf; public byte* Address; public long Size; } private FileStream fs; private List areas = new List(); private long[] offsets; private byte*[] addresses; public long Length { get { CheckDisposed(); return fs.Length; } } public GrowableMemoryMappedFile(string filePath, long initialFileSize) { if (initialFileSize <= 0 || initialFileSize % AllocationGranularity != 0) { throw new ArgumentException("The initial file size must be a multiple of 64Kb and grater than zero"); } bool existingFile = File.Exists(filePath); fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); if (existingFile) { if (fs.Length <= 0 || fs.Length % AllocationGranularity != 0) { throw new ArgumentException("Invalid file. Its lenght must be a multiple of 64Kb and greater than zero"); } } else { fs.SetLength(initialFileSize); } CreateFirstArea(); } private void CreateFirstArea() { var mmf = MemoryMappedFile.CreateFromFile(fs, null, fs.Length, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, true); var address = Win32FileMapping.MapViewOfFileEx(mmf.SafeMemoryMappedFileHandle.DangerousGetHandle(), Win32FileMapping.FileMapAccess.Read | Win32FileMapping.FileMapAccess.Write, 0, 0, new UIntPtr((ulong) fs.Length), null); if (address == null) throw new Win32Exception(); var area = new MemoryMappedArea { Address = address, Mmf = mmf, Size = fs.Length }; areas.Add(area); addresses = new byte*[] { address }; offsets = new long[] { 0 }; } public void Grow(long bytesToGrow) { CheckDisposed(); if (bytesToGrow <= 0 || bytesToGrow % AllocationGranularity != 0) { throw new ArgumentException("The growth must be a multiple of 64Kb and greater than zero"); } long offset = fs.Length; fs.SetLength(fs.Length + bytesToGrow); var mmf = MemoryMappedFile.CreateFromFile(fs, null, fs.Length, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, true); uint* offsetPointer = (uint*)&offset; var lastArea = areas[areas.Count - 1]; byte* desiredAddress = lastArea.Address + lastArea.Size; var address = Win32FileMapping.MapViewOfFileEx(mmf.SafeMemoryMappedFileHandle.DangerousGetHandle(), Win32FileMapping.FileMapAccess.Read | Win32FileMapping.FileMapAccess.Write, offsetPointer[1], offsetPointer[0], new UIntPtr((ulong)bytesToGrow), desiredAddress); if (address == null) { address = Win32FileMapping.MapViewOfFileEx(mmf.SafeMemoryMappedFileHandle.DangerousGetHandle(), Win32FileMapping.FileMapAccess.Read | Win32FileMapping.FileMapAccess.Write, offsetPointer[1], offsetPointer[0], new UIntPtr((ulong)bytesToGrow), null); } if (address == null) throw new Win32Exception(); var area = new MemoryMappedArea { Address = address, Mmf = mmf, Size = bytesToGrow }; areas.Add(area); if (desiredAddress != address) { offsets = offsets.Add(offset); addresses = addresses.Add(address); } } public byte* GetPointer(long offset) { CheckDisposed(); int i = offsets.Length; if (i <= 128) // linear search is more efficient for small arrays. Experiments show 140 as the cutpoint on x64 and 100 on x86. { while (--i > 0 && offsets[i] > offset); } else // binary search is more efficient for large arrays { i = Array.BinarySearch(offsets, offset); if (i < 0) i = ~i - 1; } return addresses[i] + offset - offsets[i]; } private bool isDisposed; public void Dispose() { if (isDisposed) return; isDisposed = true; foreach (var a in this.areas) { Win32FileMapping.UnmapViewOfFile(a.Address); a.Mmf.Dispose(); } fs.Dispose(); areas.Clear(); } private void CheckDisposed() { if (isDisposed) throw new ObjectDisposedException(this.GetType().Name); } public void Flush() { CheckDisposed(); foreach (var area in areas) { if (!Win32FileMapping.FlushViewOfFile(area.Address, new IntPtr(area.Size))) { throw new Win32Exception(); } } fs.Flush(true); } } } 

这是Win32FileMapping类:

 using System; using System.Runtime.InteropServices; namespace MmbpTree { public static unsafe class Win32FileMapping { [Flags] public enum FileMapAccess : uint { Copy = 0x01, Write = 0x02, Read = 0x04, AllAccess = 0x08, Execute = 0x20, } [DllImport("kernel32.dll", SetLastError = true)] public static extern byte* MapViewOfFileEx(IntPtr mappingHandle, FileMapAccess access, uint offsetHigh, uint offsetLow, UIntPtr bytesToMap, byte* desiredAddress); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool UnmapViewOfFile(byte* address); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool FlushViewOfFile(byte* address, IntPtr bytesToFlush); } } 

在这里你有Extensions类:

 using System; namespace MmbpTree { public static class Extensions { public static T[] Add(this T[] array, T element) { var result = new T[array.Length + 1]; Array.Copy(array, result, array.Length); result[array.Length] = element; return result; } public static unsafe byte*[] Add(this byte*[] array, byte* element) { var result = new byte*[array.Length + 1]; Array.Copy(array, result, array.Length); result[array.Length] = element; return result; } } } 

如你所见,我采取了不安全的方法。 这是获得内存映射文件的性能优势的唯一方法。

要使用此function,您需要考虑以下概念:

  • 页面 。 这是您使用的连续内存地址和存储空间的最小区域。 块或页面的大小必须是底层系统页面大小( 4Kb )的倍数。
  • 初始文件大小 。 它必须是块或页面大小的倍数,并且必须是系统分配粒度( 64Kb )的倍数。
  • 文件增长 。 它必须是块或页面大小的倍数,并且必须是系统分配粒度( 64Kb )的倍数。

例如,您可能希望使用页面大小为1Mb,文件增长为64Mb,初始大小为1Gb。 您可以通过调用GetPointer获取指向页面的指针,使用Grow增长文件并使用Flush刷新文件:

 const int InitialSize = 1024 * 1024 * 1024; const int FileGrowth = 64 * 1024 * 1024; const int PageSize = 1024 * 1024; using (var gmmf = new GrowableMemoryMappedFile("mmf.bin", InitialSize)) { var pageNumber = 32; var pointer = gmmf.GetPointer(pageNumber * PageSize); // you can read the page content: byte firstPageByte = pointer[0]; byte lastPageByte = pointer[PageSize - 1]; // or write it pointer[0] = 3; pointer[PageSize -1] = 43; /* allocate more pages when needed */ gmmf.Grow(FileGrowth); /* use new allocated pages */ /* flushing the file writes to the underlying file */ gmmf.Flush(); } 

代码无法编译的原因是因为它使用了不存在的重载。 要么自己创建一个文件流并将其传递给正确的重载(假设2000将是你的新大小):

 FileStream fs = new FileStream("C:\MyFile.dat", FileMode.Open); MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, "someName", 2000, MemoryMappedFileAccess.ReadWriteExecute, null, HandleInheritablity.None, false); 

或者使用此重载跳过filstream创建:

 MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile("C:\MyFile.dat", FileMode.Open, "someName", 2000); 

使用带有capacity参数的MemoryMappedFile.CreateFromFile的重载。