Как быстро прочитать байты из файла с отображением памяти в .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:
Небезопасный метод
Из небольшого количества тестов это выглядит так: MemoryMappedViewStream
имеет очень небольшое преимущество. Имея это в виду, для тех, кто читает этот пост по дороге, я бы пошел с MemoryMappedViewStream
.