#c# #.net-4.0 #memory-mapped-files
#c# #.net-4.0 #файлы с отображением в памяти
Вопрос:
Я использовал C # для решения следующего требования.. — создайте приложение, которое может быстро получать много данных — вы должны иметь возможность анализировать полученные данные во время поступления новых. — используйте как можно меньше процессора и диска
Моя идея для алгоритма была..
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
-> Размер на диске увеличивается на 10 МБ при использовании предыдущего «места».
Как «увеличить mmf.size на РАЗМЕР» выполняется в C #? Я нашел много простых примеров по созданию mmfs и представлений, но единственное место (ссылка) Я видел код, который значительно увеличивает область mmfs, использует код, который не может скомпилироваться. Любая помощь будет высоко оценена.
РЕДАКТИРОВАТЬ Это вызывает исключение :
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);
}
Возникает это исключение: процесс не может получить доступ к файлу ‘C:UsersmobergDocumentsdata.bin ‘ потому что он используется другим процессом.
Комментарии:
1. Почему код на этой странице не компилируется? Для меня это выглядит допустимым.
2. Он использует несуществующую перегрузку — «MemoryMappedFile. CreateFromFile(file, null, 1000);»
3. Процесс не может получить доступ к файлу ‘C:UsersmolsgaarDocumentsdata.bin ‘ потому что он используется другим процессом.
4. Обязательно ли это делать с использованием MMF? Не могли бы вы не использовать обычный доступ к файлу — создать или открыть файл для добавления и просто продолжать записывать данные в конец файла (который затем будет автоматически увеличиваться). Не могли бы вы, возможно, дать больше контекста о том, как данные должны анализироваться, или что будет их анализировать?
Ответ №1:
После того, как вы отобразили файл в памяти, вы не можете увеличить его размер.Это известное ограничение файлов с отображением в памяти.
… необходимо вычислить или оценить размер готового файла, поскольку объекты сопоставления файлов имеют статический размер; после создания их размер нельзя увеличить или уменьшить.
Одной из стратегий было бы использование фрагментов, хранящихся в непостоянных файлах с отображением в память заданного размера, скажем, 1 ГБ или 2 ГБ. Вы могли бы управлять ими с помощью верхнего уровня ViewAccessor
вашего собственного дизайна (вероятно, выполняя базовую обработку методов, которые вам нужны из MemoryMappedViewAccessor
).
Редактировать: или вы могли бы просто создать файл с отображением в несохраняемой памяти максимального размера, который вы ожидаете использовать (скажем, 8 ГБ для начала, с параметром для настройки его при запуске вашего приложения) и извлекать MemoryMappedViewAccessor
файлы для каждого логического блока. Непостоянный файл не будет использовать физические ресурсы, пока не будет запрошен каждый просмотр.
Комментарии:
1. Спасибо. У меня было ощущение, что я вмешиваюсь в область, немного мне неизвестную.
2.Вы можете увеличить их размер с помощью
NtExtendSection
undocumented.ntinternals.net/UserMode/Undocumented Functions /…3. @Matt Похоже, что в вашей ссылке конкретно упоминается, что API ‘shared (memory) section’ в
ntdll.dll
не применяется к файлам с отображением в памяти, поддерживаемым файловой системой… «Если section — это [так в оригинале] отображенный файл, функция завершается с ошибкой». Больше информации по этой (сомнительной) ссылке .
Ответ №2:
Что ж, ты можешь!!.
Вот моя реализация файла с отображением в памяти с возможностью увеличения:
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*)amp;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 amp;amp; 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);
}
}
}
Вот 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);
}
}
И вот у вас есть 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 resu<
}
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 resu<
}
}
}
Как вы можете видеть, я использую небезопасный подход. Это единственный способ получить преимущества в производительности файлов с отображением в памяти.
Для работы с этим вам необходимо рассмотреть следующие концепции:
- Блок или страница. Это ваша минимальная область непрерывного адреса памяти и дискового пространства, с которым вы работаете. Размер блока или страницы должен быть кратен размеру базовой системной страницы (4 КБ).
- Начальный размер файла. Он должен быть кратен размеру блока или страницы и должен быть кратен степени детализации системного распределения (64 КБ).
- Рост файла. Он должен быть кратен размеру блока или страницы и должен быть кратен степени детализации системного распределения (64 КБ).
Например, вы можете захотеть работать с размером страницы 1 МБ, объемом файла 64 Мб и начальным размером 1 ГБ. Вы можете получить указатель на страницу, вызвав GetPointer
, увеличить файл с помощью Grow
и очистить файл с помощью 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();
}
Комментарии:
1. Выдает исключение при компиляции под .NET 4.0, но в остальном отлично работает.
2. Итак, как MapViewOfFileEx сравнивается с использованием MemoryMappedViewAccessor в .Net? Есть ли очень большая разница в скорости?
3. MemoryMappedViewAccessor работает даже медленнее, чем FileStream. Однако вы можете получить указатель из MemoryMappedViewAccessor, и это быстро. Но MemoryMappedFile. CreateViewAccessor не позволяет запрашивать желаемый адрес, это ключ к сохранению отображенных блоков как можно более непрерывными.
4. @GlennSlayden. Спасибо. В настоящее время я использую другой подход, вводя концепцию сеанса и переназначения, когда непрерывная память не может быть выделена, таким образом, у меня всегда есть весь файл, отображенный в непрерывную память. Взгляните на любимый проект, который я разрабатываю github.com/jesuslpm/PersistentHashing
5. Существуют ли эквивалентные внешние вызовы для Linux? Я знаю о
mmap
, я просто не уверен, как собрать это воедино.
Ответ №3:
Причина, по которой код не компилируется, заключается в том, что он использует несуществующую перегрузку. Либо создайте filestream самостоятельно и передайте его правильной перегрузке (предполагая, что 2000 будет вашим новым размером):
FileStream fs = new FileStream("C:MyFile.dat", FileMode.Open);
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, "someName", 2000,
MemoryMappedFileAccess.ReadWriteExecute, null, HandleInheritablity.None, false);
Или используйте эту перегрузку, чтобы пропустить создание filstream:
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile("C:MyFile.dat",
FileMode.Open, "someName", 2000);
Комментарии:
1. при этом я получаю нарушение доступа — даже если я удаляю старый mmf перед его переназначением..
2. @Moberg: Какой из двух методов вы используете, и что говорит вам исключение?
Ответ №4:
Я обнаружил, что закрытие и воссоздание mmf с тем же именем, но нового размера работает во всех отношениях
using (var mmf = MemoryMappedFile.CreateOrOpen(SenderMapName, 1))
{
mmf.SafeMemoryMappedFileHandle.Close();
}
using (var sender = MemoryMappedFile.CreateNew(SenderMapName, bytes.Length))
и это действительно быстро.
Ответ №5:
Используйте перегрузку, MemoryMappedFile.CreateFromFile
которая принимает capacity
параметр.