c# - Cómo expandir dinámicamente un archivo asignado en memoria
.net-4.0 memory-mapped-files (4)
He utilizado C # para resolver el requisito de seguimiento ... - cree una aplicación que pueda recibir una gran cantidad de datos rápidamente - debe poder analizar los datos recibidos mientras hay más datos disponibles. - Utilice la menor cantidad de CPU y disco posible
Mi idea para un algoritmo fue ...
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
-> El tamaño del disco se incrementa en bloques de 10 MB cuando se utiliza la "sala / espacio" anterior.
¿Cómo se realiza el "aumentar mmf.size por TAMAÑO" en C #? He encontrado muchos ejemplos simples sobre la creación de mmfs y vistas, pero el único lugar ( link ) que he visto código que incrementa de forma aguda el área de mmfs usa código que no se puede compilar. Cualquier ayuda será muy apreciada.
EDITAR Esto causa una excepción:
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);
}
Se produce esta excepción: el proceso no puede acceder al archivo ''C: / Users / moberg / Documents / data.bin'' porque otro proceso lo está utilizando.
Bueno, tu puedes !! .
Aquí está mi implementación de un archivo mapeado de memoria ampliable:
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);
}
}
}
Aquí está la clase Win32FileMapping
:
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);
}
}
Y aquí tienes la clase de Extensions
:
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;
}
}
}
Como pueden ver, yo tomo el enfoque inseguro. Es la única forma de obtener los beneficios de rendimiento de los archivos asignados en memoria.
Para trabajar con esto necesitas considerar los siguientes conceptos:
- El bloque o página . Esta es su región mínima de dirección de memoria continua y espacio de almacenamiento con el que trabaja. El tamaño de un bloque o página debe ser un múltiplo del tamaño de la página del sistema subyacente ( 4Kb ).
- El tamaño del archivo inicial . Debe ser un múltiplo del bloque o el tamaño de la página y debe ser un múltiplo de la granularidad de asignación del sistema ( 64Kb ).
- El archivo de crecimiento . Debe ser un múltiplo del bloque o el tamaño de la página y debe ser un múltiplo de la granularidad de asignación del sistema ( 64Kb ).
Por ejemplo, es posible que desee trabajar con un tamaño de página de 1Mb, un crecimiento de archivos de 64Mb y un tamaño inicial de 1Gb. Puede obtener un puntero a una página llamando a GetPointer
, GetPointer
crecer el archivo usando Grow
y vacíe el archivo con 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();
}
La razón por la que el código no se compila es porque usa una sobrecarga no existente. O bien cree un flujo de archivos y transfiéralo a la sobrecarga correcta (suponiendo que 2000 será su nuevo tamaño):
FileStream fs = new FileStream("C:/MyFile.dat", FileMode.Open);
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, "someName", 2000,
MemoryMappedFileAccess.ReadWriteExecute, null, HandleInheritablity.None, false);
O use esta sobrecarga para omitir la creación del flujo de archivos:
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile("C:/MyFile.dat",
FileMode.Open, "someName", 2000);
Utilice la sobrecarga de MemoryMappedFile.CreateFromFile
que toma un parámetro de capacity
.
Una vez que asigna un archivo en la memoria, no puede aumentar su tamaño. Esta es una limitación conocida de los archivos asignados a la memoria.
... debe calcular o estimar el tamaño del archivo terminado porque los objetos de asignación de archivos tienen un tamaño estático; Una vez creado, su tamaño no puede ser aumentado o disminuido.
Una estrategia sería usar trozos almacenados en archivos mapeados de memoria no persistentes de un tamaño determinado , por ejemplo 1GB o 2GB. Puede administrarlos a través de un ViewAccessor
de nivel ViewAccessor
de su propio diseño (probablemente haciendo un recorrido básico de los métodos que necesita de MemoryMappedViewAccessor
).
Edición: o simplemente puede crear un archivo de memoria no persistente del tamaño máximo que espera usar (digamos 8GB para comenzar, con un parámetro para sintonizarlo al inicio de su aplicación) y recuperar el fragmento lógico de MemoryMappedViewAccessor . El archivo no persistente no utilizará recursos físicos hasta que se solicite cada vista.