【问题标题】:How can I quickly read bytes from a memory mapped file in .NET?如何从 .NET 中的内存映射文件中快速读取字节?
【发布时间】:2011-10-31 15:55:14
【问题描述】:

在某些情况下,MemoryMappedViewAccessor 类并不能有效地读取字节;我们得到的最好的是通用的ReadArray<byte>,它是所有结构的路由,当您只需要字节时涉及几个不必要的步骤。

可以使用MemoryMappedViewStream,但是因为它是基于Stream,所以你需要先寻找到正确的位置,然后读取操作本身有很多不必要的步骤。

是否有一种快速、高性能的方法可以从 .NET 中的内存映射文件中读取字节数组,前提是它应该只是要读取的地址空间的特定区域?

【问题讨论】:

    标签: c# .net memory-mapped-files


    【解决方案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.ElapsedMilliseconds}ms" );
                }
            }
    

    我对每种方法进行了 3 次测试,并收到了以下次数。

    MemoryMappedViewStream:

    1. 483ms
    2. 501 毫秒
    3. 490 毫秒

    不安全的方法

    1. 531ms
    2. 517ms
    3. 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&lt;byte&gt; 慢得多,但是
    猜你喜欢
    • 2014-12-14
    • 2010-09-17
    • 1970-01-01
    • 2015-11-07
    • 2012-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多