Jak mogę szybko odczytać bajty z pliku mapowanego pamięcią in.NET?

W niektórych sytuacjach klasa MemoryMappedViewAccessor po prostu nie tnie jej do efektywnego odczytywania bajtów; najlepsze, co otrzymujemy, to ogólna ReadArray<byte>, która jest trasą dla wszystkich struktur i obejmuje kilka niepotrzebnych kroków, gdy potrzebujesz bajtów.

Możliwe jest użycie MemoryMappedViewStream, ale ponieważ jest ona oparta na Stream, Musisz najpierw szukać właściwej pozycji, a następnie sama operacja odczytu ma o wiele więcej niepotrzebnych kroków.

Czy istnieje szybki, wydajny sposób odczytu tablicy bajtów z pliku mapowanego pamięcią w. NET, biorąc pod uwagę, że powinien to być tylko określony obszar przestrzeni adresowej do odczytu?

Author: casperOne, 2011-10-31

4 answers

To rozwiązanie wymaga niebezpiecznego kodu (kompilacji z /unsafe switch), ale chwyta wskaźnik bezpośrednio do pamięci; wtedy można użyć Marshal.Copy. Jest to znacznie, znacznie szybsze niż metody dostarczane przez. 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();
    }
 28
Author: Kieren Johnstone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-31 15:59:18

Zobacz Raport o błędzie: Brak możliwości określenia wewnętrznego przesunięcia używanego przez MemoryMappedViewAccessor - sprawia, że właściwość SafeMemoryMappedViewHandle jest bezużyteczna.

Z raportu:

MemoryMappedViewAccessor ma właściwość SafeMemoryMappedViewHandle, która zwraca ViewHandle używane wewnętrznie przez MemoryMappedView, ale nie ma żadnej właściwości zwracającej przesunięcie używane przez MemoryMappedView.

Jak MemoryMappedView jest strona wyrównująca przesunięcie wymagane w MemoryMappedFile.CreateViewAccessor (offset,size) nie można użyć SafeMemoryMappedViewHandle do niczego użytecznego bez znajomości offsetu.

Zauważ, że tak naprawdę chcemy użyć metody AcquirePointer (ref byte * pointer), aby umożliwić uruchamianie szybkiego kodu opartego na wskaźnikach (prawdopodobnie niezarządzanego). Nie przeszkadza nam, że wskaźnik jest wyrównany do strony, ale musi być możliwe, aby dowiedzieć się, jakie przesunięcie od pierwotnie żądanego adresu jest.

 2
Author: nim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-25 06:26:27

Bezpieczna wersja tego rozwiązania to:

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

Przetestowałem to, to działa. Nie mogę skomentować jego wydajności lub czy jest to najlepsze ogólne rozwiązanie, tylko że działa.

 1
Author: Adam Venturella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-24 20:19:38

Wiem, że to starsze pytanie, na które odpowiedziano, ale chciałem dodać moje dwa grosze.

Przeprowadziłem test zarówno z zaakceptowaną odpowiedzią (używając kodu unsafe), jak i z podejściem MemoryMappedViewStream do odczytu tablicy 200MB bajtów.

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

Przeprowadziłem test 3 razy z każdym podejściem i otrzymałem następujące razy.

MemoryMappedViewStream:

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

Metoda niebezpieczna

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

Z małej ilości testów wygląda na to, że MemoryMappedViewStream mabardzo niewielką przewagę . Mając to na uwadze dla każdego, kto czyta ten post po drodze, wybrałbym MemoryMappedViewStream.

 1
Author: WBuck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-02 23:56:45