How to dynamically expand a Memory Mapped File

后端 未结 5 1925
星月不相逢
星月不相逢 2020-12-09 08:16

I\'ve used C# to solve the following requirement.. - create an app the can receive a lot of data fast - you must be able to analyse the received data while more are incoming

相关标签:
5条回答
  • 2020-12-09 08:36

    The reason that the code does not compile is because it uses a non-existing overload. Either create a filestream yourself and pass it to the correct overload (assuming 2000 will be your new size):

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

    Or use this overload to skip the filstream creation:

    MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile("C:\MyFile.dat", 
              FileMode.Open, "someName", 2000);
    
    0 讨论(0)
  • 2020-12-09 08:48

    I found that closing and recreating the mmf with the same name but new size works to all intents and purposes

                    using (var mmf = MemoryMappedFile.CreateOrOpen(SenderMapName, 1))
                    {
                        mmf.SafeMemoryMappedFileHandle.Close();
                    }
                    using (var sender = MemoryMappedFile.CreateNew(SenderMapName, bytes.Length))
    
    

    and it's really fast.

    0 讨论(0)
  • 2020-12-09 08:50

    Once you map a file in memory, you cannot increase its size. This is a known limitation of memory mapped files.

    ...you must calculate or estimate the size of the finished file because file mapping objects are static in size; once created, their size cannot be increased or decreased.

    One strategy would be to use chunks stored in non-persisted memory mapped files of a given size, say 1GB or 2GB. You would manage these through a top level ViewAccessor of your own design (probably doing basic passthru of the methods you need from the MemoryMappedViewAccessor).

    Edit: or you could just create a non-persisted memory mapped file of a maximal size you expect to use (say 8GB to start, with a parameter to tune it on start-up of your application) and retrieve MemoryMappedViewAccessor's per logical chunk. The non-persisted file will not use physical resources until each view is requested.

    0 讨论(0)
  • 2020-12-09 08:58

    Well, you can!!.

    Here is my implementation of a growable memory mapped file:

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

    Here is the Win32FileMapping class:

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

    And here you have the Extensions class:

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

    As you can see I take the unsafe approach. It's the only way to get the performance benefits of memory mapped files.

    To work with this you need to consider the following concepts:

    • The block or page. This is your minimal region of continuous memory address and storage space you work with. The size of a block or page must be a multiple of underlying system page size (4Kb).
    • The initial file size. It must be a multiple of the block or page size and it must be a multiple of the system allocation granularity (64Kb).
    • The file growth. It must be a multiple of the block or page size and it must be a multiple of the system allocation granularity (64Kb).

    For example you may want to work with a page size of 1Mb, a file growth of 64Mb and an initial size of 1Gb. You can get a pointer to a page by calling GetPointer, grow the file using Grow and flush the file using 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();
    
    }
    
    0 讨论(0)
  • 2020-12-09 09:02

    Use the overload of MemoryMappedFile.CreateFromFile that takes a capacity parameter.

    0 讨论(0)
提交回复
热议问题