如何快速读取字节从内存映射文件在.net

本文关键字:映射 文件 net 内存 何快速 读取 字节 | 更新日期: 2023-09-27 18:12:58

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

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

是否有一种快速,高性能的方法从。net中的内存映射文件中读取字节数组,假设它应该只是要读取的地址空间的特定区域?

如何快速读取字节从内存映射文件在.net

这种解决方案需要不安全的代码(使用/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();
    }

查看此bug报告:无法确定MemoryMappedViewAccessor使用的内部偏移量-使SafeMemoryMappedViewHandle属性不可用。

摘自报告:

MemoryMappedViewAccessor有一个SafeMemoryMappedViewHandle属性,它返回被MemoryMappedView内部使用的ViewHandle,但是没有任何属性返回被MemoryMappedView使用的偏移量。

由于MemoryMappedView是对齐MemoryMappedFile.CreateViewAccessor(offset,size)中请求的偏移量的页面,因此不可能在不知道偏移量的情况下使用SafeMemoryMappedViewHandle进行任何有用的操作。

请注意,我们实际上想做的是使用AcquirePointer(ref byte* pointer)方法来允许一些快速的基于指针的(可能是非托管的)代码运行。我们可以让指针与页面对齐,但必须有可能找出与原始请求地址的偏移量。

我知道这是一个已经回答过的老问题,但我想补充我的两点意见。

我用接受的答案(使用不安全的代码)和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. 483 ms
  2. 501 ms
  3. 490 ms

不安全的方法
  1. 531 ms
  2. 517 ms
  3. 523 ms

从少量的测试来看,MemoryMappedViewStream具有非常轻微的优势。考虑到这一点,我将选择MemoryMappedViewStream

这个解决方案的一个安全版本是:

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);

我已经测试过了,它确实有效。我不能评论它的性能,或者如果它是最好的整体解决方案,只是它的工作。

只是想与long l_offset共享一个版本(因此可以读取'写入大于int32 max大小的文件):

public static unsafe byte[] ReadBytes(long l_offset, int i_read_buf_size, MemoryMappedViewAccessor mmva)
    {
        byte[] arr = new byte[i_read_buf_size];
        byte* ptr = (byte*)0;
        mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
        IntPtr p = new(ptr);
        Marshal.Copy(new IntPtr(p.ToInt64() + l_offset), arr, 0, i_read_buf_size);
        mmva.SafeMemoryMappedViewHandle.ReleasePointer();
        return arr;
    }