Как быстро прочитать байты из файла с отображением памяти в .NET?

В некоторых ситуациях класс 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();
    }

Ответ 2

См. этот отчет об ошибке: Невозможно определить внутреннее смещение, используемое MemoryMappedViewAccessor - делает свойство SafeMemoryMappedViewHandle непригодным.

Из отчета:

MemoryMappedViewAccessor имеет свойство SafeMemoryMappedViewHandle, которое возвращает ViewHandle, которое используется внутри MemoryMappedView, но не имеет никакого свойства для возврата смещения, используемого MemoryMappedView.

Поскольку MemoryMappedView - это выравнивание страницы, запрошенное в MemoryMappedFile.CreateViewAccessor(смещение, размер), невозможно использовать SafeMemoryMappedViewHandle для чего-либо полезного, не зная смещения.

Обратите внимание, что мы действительно хотим использовать метод AcquirePointer (ref byte * pointer), чтобы разрешить запуск некоторого быстрого кода (возможно, неуправляемого). Мы в порядке, указатель выравнивается по страницам, но должно быть возможно выяснить, что такое смещение от первоначально запрошенного адреса.

Ответ 3

Безопасная версия этого решения:

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

Я тестировал это, он работает. Я не могу прокомментировать его производительность или если это лучшее решение для всего, что он работает.

Ответ 4

Я знаю, что это более старый вопрос, на который был дан ответ, но я хотел добавить свои два цента.

Я проверил тест как с принятым ответом (используя небезопасный код), так и с подходом MemoryMappedViewStream для чтения массива байтов размером 200 МБ.

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:

  • 483ms
  • 501ms
  • 490ms

Небезопасный метод

  • 531ms
  • 517ms
  • 523ms

Из небольшого количества тестов это выглядит так: MemoryMappedViewStream имеет очень небольшое преимущество. Имея это в виду, для тех, кто читает этот пост по дороге, я бы пошел с MemoryMappedViewStream.