如何从 .NET 中的内存映射文件快速读取字节?

发布于 2024-12-13 02:34:58 字数 321 浏览 10 评论 0原文

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

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

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

In some situations the MemoryMappedViewAccessor class just doesn't cut it for reading bytes efficiently; the best we get is the generic ReadArray<byte> which it the route for all structs and involves several unnecessary steps when you just need bytes.

It's possible to use a MemoryMappedViewStream, but because it's based on a Stream you need to seek to the correct position first, and then the read operation itself has many more unnecessary steps.

Is there a quick, high-performance way to read an array of bytes from a memory-mapped file in .NET, given that it should just be a particular area of the address space to read from?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

安稳善良 2024-12-20 02:34:58

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

This solution requires unsafe code (compile with /unsafe switch), but grabs a pointer to the memory directly; then Marshal.Copy can be used. This is much, much faster than the methods provided by the .NET framework.

    // 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();
    }
帝王念 2024-12-20 02:34:58

我知道这是一个较旧的问题,已得到解答,但我想补充我的两分钱。

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

不安全方法

  1. 531ms
  2. 517ms
  3. 523ms

从少量测试来看,MemoryMappedViewStream 具有非常轻微的优势。考虑到这一点,对于任何阅读这篇文章的人来说,我都会选择 MemoryMappedViewStream

I know this is an older question which has been answered but I wanted to add my two cents.

I ran a test with both the accepted answer (using the unsafe code) and with the MemoryMappedViewStream approach for reading a 200MB byte array.

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

I ran the test 3 times with each approach and received the following times.

MemoryMappedViewStream:

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

Unsafe method

  1. 531ms
  2. 517ms
  3. 523ms

From the small amount of testing it looks like the MemoryMappedViewStream has a very slight advantage. With that in mind for anyone reading this post down the road I would go with the MemoryMappedViewStream.

莫相离 2024-12-20 02:34:58

请参阅此错误报告:无法确定 MemoryMappedViewAccessor 使用的内部偏移量 - 使得SafeMemoryMappedViewHandle 属性不可用。

从报告中:

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

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

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

See this bug report: No way to determine internal offset used by MemoryMappedViewAccessor - Makes SafeMemoryMappedViewHandle property unusable.

From the report:

MemoryMappedViewAccessor has a SafeMemoryMappedViewHandle property, which returns the ViewHandle being used internally by the MemoryMappedView, but does not have any property to return the offset being used by the MemoryMappedView.

As the MemoryMappedView is page aligning the offset requested in MemoryMappedFile.CreateViewAccessor(offset,size) it is impossible to use the SafeMemoryMappedViewHandle for anything useful without knowing the offset.

Note that what we actually want to do is use the AcquirePointer(ref byte* pointer) method to allow some fast pointer based (possibly unmanaged) code to run. We're OK with the pointer being page aligned, but it must be possible to find out what the offset from the originally requested address is.

俯瞰星空 2024-12-20 02:34:58

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

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

我已经测试过这个,它确实有效。我无法评论它的性能,也无法评论它是否是最好的整体解决方案。

A safe version of this solution is:

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

I have tested this, it does work. I cannot comment on it's performance or if it's the BEST overall solution just that it works.

往日 2024-12-20 02:34:58

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

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

Just wanted to share a version with long l_offset (so it is possible to read\write files larger than int32 max size):

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;
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文