Pytanie Jak dynamicznie rozszerzać plik mapowany w pamięci


Użyłem C #, aby rozwiązać wymagania obserwowania. - Stwórz aplikację, która szybko odbierze wiele danych - musisz mieć możliwość przeanalizowania otrzymanych danych, podczas gdy kolejne są w toku. - używaj jak najmniej procesora i dysku

Mój pomysł na algorytm był ...

SIZE = 10MB
Create a mmf with the size of SIZE
On data recived:
  if data can't fit mmf: increase mmf.size by SIZE
  write the data to mmf

-> Rozmiar na dysku zwiększa się w chunckach o wielkości 10 MB, gdy używany jest poprzedni "pokój / przestrzeń".

W jaki sposób "wzrost mmf.size przez SIZE" odbywa się w języku C #? Znalazłem wiele prostych przykładów tworzenia mmfs i widoków, ale jedyne miejsce (połączyć) Widziałem kod, który acutally zwiększa obszar mmfs używa kodu, który nie może się skompilować. Każda pomoc będzie bardzo przydatna.

EDYTOWAĆ Powoduje to wyjątek:

private void IncreaseFileSize()
{
    int theNewMax = this.currentMax + INCREMENT_SIZE;
    this.currentMax = theNewMax;

    this.mmf.Dispose();

    this.mmf = MemoryMappedFile.CreateFromFile(this.FileName, FileMode.Create, "MyMMF", theNewMax);
    this.view = mmf.CreateViewAccessor(0, theNewMax);            
}

Ten wyjątek jest zgłaszany: proces nie może uzyskać dostępu do pliku "C: \ Users \ moberg \ Documents \ data.bin", ponieważ jest używany przez inny proces.


21
2018-05-23 11:21


pochodzenie


Dlaczego kod na tej stronie nie jest kompilowany? Wydaje mi się to ważne. - Edwin de Koning
Wykorzystuje nieistniejące przeciążenie - "MemoryMappedFile.CreateFromFile (file, null, 1000);" - Moberg
Czym jest wyjątek? - user7116
Proces nie może uzyskać dostępu do pliku "C: \ Users \ molsgaar \ Documents \ data.bin", ponieważ jest używany przez inny proces. - Moberg
Czy to mieć zrobić za pomocą MMF? Czy nie możesz po prostu uzyskać zwykłego dostępu do pliku - utwórz lub otwórz plik do dołączenia i po prostu zapisuj dane na końcu pliku (który będzie następnie automatycznie rósł). Czy mógłbyś podać więcej kontekstu na temat tego, jak dane mają zostać poddane analizie lub co będzie analizować? - Gavin Lock


Odpowiedzi:


Po mapowaniu pliku w pamięci nie można zwiększyć jego rozmiaru. Jest to znane ograniczenie plików mapowanych w pamięci.

... musisz obliczyć lub oszacować rozmiar gotowego pliku, ponieważ obiekty mapowania plików mają stały rozmiar; po utworzeniu ich rozmiaru nie można zwiększyć ani zmniejszyć.

Jedną ze strategii byłoby użycie porcji przechowywanych w nieodtwarzane pliki zmapowane w pamięci o danym rozmiarzepowiedzmy 1 GB lub 2 GB. Poradzisz sobie z nimi przez najwyższy poziom ViewAccessor własnego projektu (prawdopodobnie robi podstawowe passthru metod, których potrzebujesz od MemoryMappedViewAccessor).

Edytować: lub możesz po prostu utworzyć nieodtwarzany plik z mapą pamięci o maksymalnym rozmiarze, którego chcesz użyć (na przykład 8 GB na początek, z parametrem do dostrojenia go podczas uruchamiania aplikacji) i odzyskać MemoryMappedViewAccessorza logiczną porcję. Nieprzerwany plik nie będzie korzystać z zasobów fizycznych, dopóki nie zostanie zażądany każdy widok.


24
2018-05-23 14:50



Dzięki. Miałem przeczucie, że błądzę w obszarze trochę nieznanym mi. - Moberg
Możesz zwiększyć ich rozmiar za pomocą NtExtendSection  undocumented.ntinternals.net/UserMode/Undocumented%20Functions/... - Matt


Dobrze, możesz!!.

Oto moja implementacja pliku zmapowanego pliku pamięci:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.MemoryMappedFiles;

namespace MmbpTree
{
    public unsafe sealed class GrowableMemoryMappedFile : IDisposable
    {

        private const int AllocationGranularity = 64 * 1024;

        private class MemoryMappedArea
        {
            public MemoryMappedFile Mmf;
            public byte* Address;
            public long Size;
        }


        private FileStream fs;

        private List<MemoryMappedArea> areas = new List<MemoryMappedArea>();
        private long[] offsets;
        private byte*[] addresses;

        public long Length
        {
            get {
                CheckDisposed();
                return fs.Length;
            }
        }

        public GrowableMemoryMappedFile(string filePath, long initialFileSize)
        {
            if (initialFileSize <= 0 || initialFileSize % AllocationGranularity != 0)
            {
                throw new ArgumentException("The initial file size must be a multiple of 64Kb and grater than zero");
            }
            bool existingFile = File.Exists(filePath);
            fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
            if (existingFile)
            {
                if (fs.Length <=  0 || fs.Length % AllocationGranularity != 0)
                {
                    throw new ArgumentException("Invalid file. Its lenght must be a multiple of 64Kb and greater than zero");
                }
            }
            else
            { 
                fs.SetLength(initialFileSize);
            }
            CreateFirstArea();
        }

        private void CreateFirstArea()
        {
            var mmf = MemoryMappedFile.CreateFromFile(fs, null, fs.Length, MemoryMappedFileAccess.ReadWrite,  null, HandleInheritability.None, true);
            var address = Win32FileMapping.MapViewOfFileEx(mmf.SafeMemoryMappedFileHandle.DangerousGetHandle(), 
                Win32FileMapping.FileMapAccess.Read | Win32FileMapping.FileMapAccess.Write,
                0, 0, new UIntPtr((ulong) fs.Length), null);
            if (address == null) throw new Win32Exception();

            var area = new MemoryMappedArea
            {
                Address = address,
                Mmf = mmf,
                Size = fs.Length
            };
            areas.Add(area);

            addresses = new byte*[] { address };
            offsets = new long[] { 0 };

        }


        public void Grow(long bytesToGrow)
        {
            CheckDisposed();
            if (bytesToGrow <= 0 || bytesToGrow % AllocationGranularity != 0)  {
                throw new ArgumentException("The growth must be a multiple of 64Kb and greater than zero");
            }
            long offset = fs.Length;
            fs.SetLength(fs.Length + bytesToGrow);
            var mmf = MemoryMappedFile.CreateFromFile(fs, null, fs.Length, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, true);
            uint* offsetPointer = (uint*)&offset;
            var lastArea = areas[areas.Count - 1];
            byte* desiredAddress = lastArea.Address + lastArea.Size;
            var address = Win32FileMapping.MapViewOfFileEx(mmf.SafeMemoryMappedFileHandle.DangerousGetHandle(), 
                Win32FileMapping.FileMapAccess.Read | Win32FileMapping.FileMapAccess.Write,
                offsetPointer[1], offsetPointer[0], new UIntPtr((ulong)bytesToGrow), desiredAddress);
            if (address == null) {
                address = Win32FileMapping.MapViewOfFileEx(mmf.SafeMemoryMappedFileHandle.DangerousGetHandle(),
                   Win32FileMapping.FileMapAccess.Read | Win32FileMapping.FileMapAccess.Write,
                   offsetPointer[1], offsetPointer[0], new UIntPtr((ulong)bytesToGrow), null);
            }
            if (address == null) throw new Win32Exception();
            var area = new MemoryMappedArea {
                Address = address,
                Mmf = mmf,
                Size = bytesToGrow
            };
            areas.Add(area);
            if (desiredAddress != address) {
                offsets = offsets.Add(offset);
                addresses = addresses.Add(address);
            }
        }

        public byte* GetPointer(long offset)
        {
            CheckDisposed();
            int i = offsets.Length;
            if (i <= 128) // linear search is more efficient for small arrays. Experiments show 140 as the cutpoint on x64 and 100 on x86.
            {
                while (--i > 0 && offsets[i] > offset);
            }
            else // binary search is more efficient for large arrays
            {
                i = Array.BinarySearch<long>(offsets, offset);
                if (i < 0) i = ~i - 1;
            }
            return addresses[i] + offset - offsets[i];
        }

        private bool isDisposed;

        public void Dispose()
        {
            if (isDisposed) return;
            isDisposed = true;
            foreach (var a in this.areas)
            {
                Win32FileMapping.UnmapViewOfFile(a.Address);
                a.Mmf.Dispose();
            }
            fs.Dispose();
            areas.Clear();
        }

        private void CheckDisposed()
        {
            if (isDisposed) throw new ObjectDisposedException(this.GetType().Name);
        }

        public void Flush()
        {
            CheckDisposed();
            foreach (var area in areas)
            {
                if (!Win32FileMapping.FlushViewOfFile(area.Address, new IntPtr(area.Size))) {
                    throw new Win32Exception();
                }
            }
            fs.Flush(true);
        }
    }
}

Tutaj jest Win32FileMapping klasa:

using System;
using System.Runtime.InteropServices;

namespace MmbpTree
{
    public static unsafe class Win32FileMapping
    {
        [Flags]
        public enum FileMapAccess : uint
        {
            Copy = 0x01,
            Write = 0x02,
            Read = 0x04,
            AllAccess = 0x08,
            Execute = 0x20,
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern byte* MapViewOfFileEx(IntPtr mappingHandle,
                                            FileMapAccess access,
                                            uint offsetHigh,
                                            uint offsetLow,
                                            UIntPtr bytesToMap,
                                            byte* desiredAddress);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool UnmapViewOfFile(byte* address);


        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool FlushViewOfFile(byte* address, IntPtr bytesToFlush);
    }
}

I tutaj masz Extensions klasa:

using System;

namespace MmbpTree
{
    public static class Extensions
    {
        public static T[] Add<T>(this T[] array, T element)
        {
            var result = new T[array.Length + 1];
            Array.Copy(array, result, array.Length);
            result[array.Length] = element;
            return result;
        }

        public static unsafe byte*[] Add(this byte*[] array, byte* element)
        {
            var result = new byte*[array.Length + 1];
            Array.Copy(array, result, array.Length);
            result[array.Length] = element;
            return result;
        }
    }
}

Jak widać podchodzę do niebezpiecznego podejścia. Jest to jedyny sposób na uzyskanie korzyści z plików mapowanych w pamięci.

Aby pracować z tym, należy wziąć pod uwagę następujące koncepcje:

  • The blok lub strona. To jest twój minimalny obszar ciągłego adresu pamięci i przestrzeni dyskowej, z którymi pracujesz. Rozmiar bloku lub strony musi być wielokrotnością podstawowego rozmiaru strony systemu (4 KB).
  • The początkowy rozmiar pliku. Musi to być wielokrotność rozmiaru bloku lub strony i musi to być wielokrotność granularności alokacji systemu (64 KB).
  • The wzrost pliku. Musi to być wielokrotność rozmiaru bloku lub strony i musi to być wielokrotność granularności alokacji systemu (64 KB).

Na przykład możesz chcieć pracować z rozmiarem strony 1Mb, wzrostem pliku o 64Mb i początkowym rozmiarem 1Gb. Możesz uzyskać wskaźnik do strony, dzwoniąc GetPointer, rozwijaj plik za pomocą Grow i opróżnij plik za pomocą Flush:

const int InitialSize = 1024 * 1024 * 1024;
const int FileGrowth = 64 * 1024 * 1024;
const int PageSize = 1024 * 1024;
using (var gmmf = new GrowableMemoryMappedFile("mmf.bin", InitialSize))
{
    var pageNumber = 32;
    var pointer = gmmf.GetPointer(pageNumber * PageSize);

    // you can read the page content:
    byte firstPageByte = pointer[0];
    byte lastPageByte = pointer[PageSize - 1];

    // or write it
    pointer[0] = 3;
    pointer[PageSize -1] = 43;


    /* allocate more pages when needed */
    gmmf.Grow(FileGrowth);

    /* use new allocated pages */

    /* flushing the file writes to the underlying file */ 
    gmmf.Flush();

}

3
2018-03-22 13:08



Zgłasza wyjątek podczas kompilacji przeciwko .NET 4.0, ale działa inaczej. - Greg Mulvihill
A więc w jaki sposób MapViewOfFileEx porównuje się do użycia MemoryMappedViewAccessor w .Net? Czy istnieje bardzo duża różnica prędkości? - millejos
MemoryMappedViewAccessor jest nawet wolniejszy niż FileStream. Możesz jednak pobrać wskaźnik z MemoryMappedViewAccessor, a to jest szybkie. Ale MemoryMappedFile.CreateViewAccessor nie pozwala zapytać o żądany adres, jest to klucz do utrzymania zmapowanych bloków tak blisko siebie, jak to możliwe. - Jesús López


Przyczyną, dla której kod się nie kompiluje, jest to, że używa nieistniejącego przeciążenia. Utwórz sam strumień pliku i przekaż go do właściwego przeciążenia (zakładając, że 2000 będzie twoim nowym rozmiarem):

FileStream fs = new FileStream("C:\MyFile.dat", FileMode.Open);
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, "someName", 2000,
 MemoryMappedFileAccess.ReadWriteExecute, null, HandleInheritablity.None, false);

Lub użyj tego przeciążenia, aby pominąć tworzenie strumienia plików:

MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile("C:\MyFile.dat", 
          FileMode.Open, "someName", 2000);

1
2018-05-23 12:38



robiąc to, dostaję pogwałcenie dostępu - nawet jeśli usuwam stary mmf przed ponownym przypisaniem go. - Moberg
@Moberg: Którą z 2 metod używasz i co mówi ten wyjątek? - Edwin de Koning
Edytować. Przepraszam - niemożliwe do odczytania. Dodano w pytaniu. Dzięki przy okazji ... - Moberg


Użyj przeciążenia MemoryMappedFile.CreateFromFile to wymaga capacity parametr.


0
2018-05-23 12:26



Tak, ale moim problemem jest zwiększenie pliku w locie .. - Moberg