如何从 .NET 中的内存映射文件中快速读取字节?
Posted
技术标签:
【中文标题】如何从 .NET 中的内存映射文件中快速读取字节?【英文标题】:How can I quickly read bytes from a memory mapped file in .NET? 【发布时间】:2011-10-31 15:55:14 【问题描述】:在某些情况下,MemoryMappedViewAccessor
类并不能有效地读取字节;我们得到的最好的是通用的ReadArray<byte>
,它是所有结构的路由,当您只需要字节时涉及几个不必要的步骤。
可以使用MemoryMappedViewStream
,但是因为它是基于Stream
,所以你需要先寻找到正确的位置,然后读取操作本身有很多不必要的步骤。
是否有一种快速、高性能的方法可以从 .NET 中的内存映射文件中读取字节数组,前提是它应该只是要读取的地址空间的特定区域?
【问题讨论】:
【参考方案1】:这个解决方案需要不安全的代码(使用/unsafe
开关编译),但是直接抓取了一个指向内存的指针;然后可以使用Marshal.Copy
。这比 .NET 框架提供的方法快得多。
// assumes part of a class where _view is a MemoryMappedViewAccessor object
public unsafe byte[] ReadBytes(int offset, int num)
byte[] arr = new byte[num];
byte *ptr = (byte*)0;
this._view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
Marshal.Copy(IntPtr.Add(new IntPtr(ptr), offset), arr, 0, num);
this._view.SafeMemoryMappedViewHandle.ReleasePointer();
return arr;
public unsafe void WriteBytes(int offset, byte[] data)
byte* ptr = (byte*)0;
this._view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
Marshal.Copy(data, 0, IntPtr.Add(new IntPtr(ptr), offset), data.Length);
this._view.SafeMemoryMappedViewHandle.ReleasePointer();
【讨论】:
您应该使用关键执行块和 try-finally 以确保即使 Marshal.Copy 抛出异常也能运行 ReleasePointer。 好答案 =) 事实上,分析显示托管包装器比使用不安全指针访问映射内存慢 30 倍。 @MattHowells 我同意。我读到 CER 可能会影响性能,但它似乎可以忽略不计(至少在受控测试中)。无论性能影响如何,它都是此处“备注”中所述的正确使用模式; msdn.microsoft.com/en-us/library/…【参考方案2】:查看此错误报告:No way to determine internal offset used by MemoryMappedViewAccessor - Makes SafeMemoryMappedViewHandle property unusable.
来自报告:
MemoryMappedViewAccessor 有一个 SafeMemoryMappedViewHandle 属性,该属性返回 MemoryMappedView 内部使用的 ViewHandle,但没有任何属性返回 MemoryMappedView 使用的偏移量。
由于 MemoryMappedView 正在页面对齐 MemoryMappedFile.CreateViewAccessor(offset,size) 中请求的偏移量,因此在不知道偏移量的情况下不可能将 SafeMemoryMappedViewHandle 用于任何有用的事情。
请注意,我们真正想要做的是使用 AcquirePointer(ref byte* pointer) 方法来允许一些基于快速指针的(可能是非托管的)代码运行。我们可以将指针进行页面对齐,但必须可以找出与最初请求地址的偏移量是多少。
【讨论】:
看起来很傻。如果您控制视图,则不需要 .NET 告诉您偏移量,因为您指定了它。 (这就是我所做的:_view
是偏移量 0 处的访问器)
Fwiw,这段代码也已经在多台机器上进行了压力测试 [数十亿次调用,数千种不同的 MMF]
现在有数千亿的调用,以及数十万的 MMF。我的代码不会发生这个错误;)
现在似乎已修复 - 您可以访问 MemoryMappedViewAccessor 的 PointerOffset 属性来计算出与请求的偏移量相对应的正确指针地址。只需将 PointerOffset 添加到 SafeMemoryMappedViewHandle.AcquirePointer() 返回的地址【参考方案3】:
我知道这是一个已回答的老问题,但我想加两分钱。
我使用接受的答案(使用不安全代码)和 MemoryMappedViewStream 方法进行了测试,以读取 200MB 字节数组。
MemoryMappedViewStream
const int MMF_MAX_SIZE = 209_715_200;
var buffer = new byte[ MMF_VIEW_SIZE ];
using( var mmf = MemoryMappedFile.OpenExisting( "mmf1" ) )
using( var view = mmf.CreateViewStream( 0, buffer.Length, MemoryMappedFileAccess.ReadWrite ) )
if( view.CanRead )
Console.WriteLine( "Begin read" );
sw.Start( );
view.Read( buffer, 0, MMF_MAX_SIZE );
sw.Stop( );
Console.WriteLine( $"Read done - sw.ElapsedMillisecondsms" );
我对每种方法进行了 3 次测试,并收到了以下次数。
MemoryMappedViewStream:
-
483ms
501 毫秒
490 毫秒
不安全的方法
-
531ms
517ms
523ms
从少量测试来看,MemoryMappedViewStream
似乎具有非常轻微的优势。考虑到这一点,我会选择MemoryMappedViewStream
。
【讨论】:
这是有道理的,文档说使用视图流进行顺序访问,它已针对它进行了优化,而视图访问器旨在用于随机访问。【参考方案4】:此解决方案的安全版本是:
var file = MemoryMappedFile.CreateFromFile(...);
var accessor = file.CreateViewAccessor();
var bytes = new byte[yourLength];
// assuming the string is at the start of the file
// aka position: 0
// https://msdn.microsoft.com/en-us/library/dd267761(v=vs.110).aspx
accessor.ReadArray<byte>(
position: 0, // The number of bytes in the accessor at which to begin reading
array: bytes, // The array to contain the structures read from the accessor
offset: 0, // The index in `array` in which to place the first copied structure
count: yourLength // The number of structures of type T to read from the accessor.
);
var myString = Encoding.UTF8.GetString(bytes);
我已经测试过了,它确实有效。我无法评论它的性能,或者它是否是最好的整体解决方案,只是它有效。
【讨论】:
酷,是的,绝对避免使用指针 :)ReadArray<byte>
慢得多,但是以上是关于如何从 .NET 中的内存映射文件中快速读取字节?的主要内容,如果未能解决你的问题,请参考以下文章