如何动态扩展内存映射文件
Posted
技术标签:
【中文标题】如何动态扩展内存映射文件【英文标题】:How to dynamically expand a Memory Mapped File 【发布时间】:2011-05-23 11:21:44 【问题描述】:我已经使用 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# 中“将 mmf.size 增加 SIZE”是如何完成的?我发现了很多关于创建 mmfs 和视图的简单示例,但唯一的地方 (link) 我看到了实际增加 mmfs 区域的代码使用无法编译的代码。任何帮助将不胜感激。
编辑 这会导致异常:
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);
抛出此异常:进程无法访问文件“C:\Users\moberg\Documents\data.bin”,因为它正被另一个进程使用。
【问题讨论】:
为什么该页面上的代码无法编译?对我来说它看起来有效。 它使用了一个不存在的重载 - "MemoryMappedFile.CreateFromFile(file, null, 1000);" 进程无法访问文件'C:\Users\molsgaar\Documents\data.bin',因为它正被另一个进程使用。 这必须使用 MMF 完成吗?您能否不只是常规文件访问 - 创建或打开一个文件以进行追加,然后继续将数据写入文件末尾(然后将自动增长)。您能否就如何分析数据或分析数据提供更多背景信息? 【参考方案1】:Once you map a file in memory, you cannot increase its size.这是内存映射文件的已知限制。
...您必须计算或估计完成文件的大小,因为文件映射对象的大小是静态的;一旦创建,它们的大小就不能增加或减少。
一种策略是使用存储在non-persisted memory mapped files of a given size 中的块,例如 1GB 或 2GB。您将通过您自己设计的*** ViewAccessor
来管理这些(可能从 MemoryMappedViewAccessor
执行您需要的方法的基本传递)。
编辑: 或者您可以创建一个您希望使用的最大大小的非持久内存映射文件(例如 8GB 开始,并带有一个参数来调整它在您的启动应用程序)并检索每个逻辑块的MemoryMappedViewAccessor
。在请求每个视图之前,非持久文件不会使用物理资源。
【讨论】:
谢谢。我有一种感觉,我在一个对我来说有点陌生的领域搞砸了。 你可以用NtExtendSection
undocumented.ntinternals.net/UserMode/Undocumented%20Functions/…增加它们的大小
@Matt 您的链接似乎特别提到ntdll.dll
中的“共享(内存)部分”API 不适用于文件系统支持的内存映射文件......“如果部分它是 [原文如此。] 映射文件,功能失败。”更多信息请访问this (dubious) link。【参考方案2】:
嗯,你可以!!。
这是我对可增长内存映射文件的实现:
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<MemoryMappedArea> areas = new List<MemoryMappedArea>();
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<long>(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<T>(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;
如您所见,我采用了不安全的方法。这是获得内存映射文件的性能优势的唯一方法。
要解决这个问题,您需要考虑以下概念:
屏蔽或页面。这是您使用的连续内存地址和存储空间的最小区域。块或页面的大小必须是底层系统页面大小 (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();
【讨论】:
针对 .NET 4.0 编译时抛出异常,但在其他情况下效果很好。 那么 MapViewOfFileEx 与在 .Net 中使用 MemoryMappedViewAccessor 相比如何?速度差异很大吗? MemoryMappedViewAccessor 甚至比 FileStream 还要慢。但是,您可以从 MemoryMappedViewAccessor 获取指针,而且速度很快。但是 MemoryMappedFile.CreateViewAccessor 不允许您请求所需的地址,这是保持映射块尽可能连续的关键。 @GlennSlayden。谢谢你。目前我正在使用另一种方法,在无法分配连续内存时引入会话和重新映射的概念,以某种方式我总是将整个文件映射到连续内存。看看我正在开发的一个宠物项目github.com/jesuslpm/PersistentHashing 好吧,事实上我已经如此了。执行代码建议的正确方法是在 MapViewOfFile3、VirtualAlloc2 等中利用MEM_RESERVE_PLACEHOLDER
和 MEM_REPLACE_PLACEHOLDER
...【参考方案3】:
代码无法编译的原因是它使用了不存在的重载。 要么自己创建一个文件流并将其传递给正确的重载(假设 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);
【讨论】:
这样做时我会遇到访问冲突 - 即使我在重新分配旧 mmf 之前将其处理掉.. @Moberg:你使用了这两种方法中的哪一种,异常告诉你什么?【参考方案4】:我发现关闭并重新创建具有相同名称但新大小的 mmf 适用于所有意图和目的
using (var mmf = MemoryMappedFile.CreateOrOpen(SenderMapName, 1))
mmf.SafeMemoryMappedFileHandle.Close();
using (var sender = MemoryMappedFile.CreateNew(SenderMapName, bytes.Length))
而且速度真的很快。
【讨论】:
【参考方案5】:使用带有capacity
参数的MemoryMappedFile.CreateFromFile
的重载。
【讨论】:
以上是关于如何动态扩展内存映射文件的主要内容,如果未能解决你的问题,请参考以下文章
v85.01 鸿蒙内核源码分析(内存池管理) | 如何高效切割合并内存块 | 百篇博客分析OpenHarmony源码