Введение в Memory-Mapped Files
Автор: Vipul Patel
Введение
В .NET Framework есть API-интерфейсы для редактирования файла, однако API, поставляемые в версиях до .NET Framework 4.0 не предназначены для работы с очень большими файлами. В потоковом классе MemoryStream (в отличие от FileStream) нет буферизации инкапсулированных данных и они напрямую помещаются в память. При отсутствии резервной копии данных, MemoryStream может оказаться полезным в качестве временного буфера. Memory-Mapped Files описываются в MSDN как способ, позволяющий приложениям работать с файлами так же как они работают с динамической памятью. Полная статья по Управлению отображаемыми в память файлами находится на сайте MSDN.
Типы отображаемых в памяти файлов
В библиотеке .NET framework 4.0 классы для работы с memory-mapped files находятся в пространстве имён System.IO.MemoryMappedFiles. Есть два основных типа отображаемых в память файлов:
- Persisted Files (Сохраняемые файлы) - Эти файлы имеют связанный с ними физический файл на диске. Эти типы файлов используются при работе с очень большими объёмами. При этом в память загружается только часть такого файла.
- Non-persisted files (Не сохраняемые файлы) - Эти файлы не имеют связанного с ними физического файла на диске, и при завершении процесса их содержимое теряется. Обычно такие файлы используются для взаимодействия между процессами и называются IPC.
Преимущества отображаемых в памяти файлов
Одним из основных преимуществ использования memory-mapped files, это увеличение производительности при операциях чтения/записи в загруженный в память файл. Доступ к памяти происходит быстрее, чем дисковые операции ввода/вывода, следовательно, повышение производительности достигается и при работе с очень большими файлами.
Отображаемые в памяти файлы также предлагают отложенную загрузку, что требует использрования небольшого количества оперативной памяти даже для больших файлов. В таких случаях приложение работает с данными в файле через так называемые отображения (вьюхой), которые физически загружают с диска всего одну страницу данных в текущий момент. Способность создавать представления позволяет значительно уменьшить потребление памяти в приложении.
Недостатки Memory-Mapped Files
Если представление для работы с файлом будет создано непродуманно с загрузкой большого количества данных, то это может сказаться на стартовой производительности приложения.
Работа с отображаемыми в память файлами
Работа с отображаемыми в память файлами происходит путём создания представления части или полного содержимого файла.
В 32-битных операционных системах, так как использование оперативной памяти ограничено двумя гигабайтами, может потребоваться создание несколько представлений.
Если для чтения последовательности данных из физического файла Вы используете потоковое чтение в память, то необходимо будет воспользоваться потоком MemoryMappedViewStream, который можно создать вызовом метода CreateViewStream объекта MemoryMappedFile.
Для IPC, Ваш отображаемый в память файл потребует отображение MemoryMappedViewAccessor имеющее доступ к оперативной памяти, и может быть создано вызовом метода CreateViewAccessor в экземпляре MemoryMappedFile.
Пример приложения с использованием Сохраняемого Memory Mapped File
В нижеприведённом фрагменте кода мы создаем сохраняемый файл, связанный с соответствующим физическим файлом на диске, доступ к которому осуществляется последовательно:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.MemoryMappedFiles;
namespace PersistentMemoryMappedFile
{
class Program
{
static string LargeString = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000.";
static void Main(string[] args)
{
CreateLargeFile();
CreateMemoryMappedFile("largetextfile.txt");
}
private static void CreateMemoryMappedFile(string fileName)
{
FileInfo fInfo = new FileInfo(fileName);
long length = fInfo.Length;
MemoryMappedFile mySequentialMMF = MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, "MySequentialMap");
for (long i = 0; i < length; i = i + 100000)
{
MemoryMappedViewStream myMMFViewStream = mySequentialMMF.CreateViewStream(0, 100000, MemoryMappedFileAccess.ReadWrite);
myMMFViewStream.WriteByte((byte)'A');
Console.WriteLine("We changed a bit");
}
}
private static void CreateLargeFile()
{
using (StreamWriter sw = new StreamWriter("largetextfile.txt", false))
{
for (int i = 0; i < 500000; i++)
sw.WriteLine(LargeString);
}
File.Copy("largetextfile.txt", "largetextfileoriginal.txt");
}
}
}
Листинг 1
В приведенном выше фрагменте кода сперва создаётся большой файл, а затем с помощью MemoryMappedFile меняется каждый стотысячный байт в потоке на "А". Если у вас есть программы умеющие работать с большими файлами, то Вы заметите разницу.
Пример MemoryMappedFile для межпроцессного взаимодействия
Давайте посмотрим пример использования отображаемой памяти файлов для связи между процессами. В примере приведены два приложения: одно записывает, а другое считывает. Листинг записывающего приложения:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.MemoryMappedFiles;
using System.IO;
using System.Threading;
namespace NonPersistedMemoryMappedFileA
{
class Program
{
static void Main(string[] args)
{
MemoryMappedFile myNonPersisterMemoryMappedFile = MemoryMappedFile.CreateNew("myNonPersisterMemoryMappedFile", 100);
bool IsmutexCreated;
Mutex mymutex = new Mutex(true, "NonPersisterMemoryMappedFilemutex", out IsmutexCreated);
Console.WriteLine("Start NonPersisterMemoryMappedFileB now");
Console.ReadLine();
StreamWriter sw = new StreamWriter(myNonPersisterMemoryMappedFile.CreateViewStream());
sw.WriteLine("Process A says Hi");
sw.Close();
mymutex.ReleaseMutex();
}
}
}
Листинг 2 - NonPersistedMemoryMappedFileB.cs
Компилируется вышеприведённый код в NonPersistedMemoryMappedFileA.exe с помощью следующей команды:
C:\windows\microsoft.net\frame work\v4.0.30319\csc.exe NonPersistedMemoryMappedFileA.cs
Теперь листинг читающего приложения:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.MemoryMappedFiles;
using System.IO;
using System.Threading;
namespace NonPersistedMemoryMappedFileB
{
class Program
{
static void Main(string[] args)
{
MemoryMappedFile myNonPersisterMemoryMappedFile = MemoryMappedFile.OpenExisting("myNonPersisterMemoryMappedFile");
Mutex mymutex = Mutex.OpenExisting("NonPersisterMemoryMappedFilemutex");
mymutex.WaitOne();
StreamReader sr = new StreamReader(myNonPersisterMemoryMappedFile.CreateViewStream());
Console.WriteLine(sr.ReadLine());
sr.Close();
mymutex.ReleaseMutex();
StreamWriter sw = new StreamWriter(myNonPersisterMemoryMappedFile.CreateViewStream());
sw.WriteLine("Process B says Hi");
sw.Close();
}
}
}
Листинг 3 - NonPersistedMemoryMappedFileB.cs
Этот код компилируется в NonPersistedMemoryMappedFileB.exe с помощью следующей команды:
C:\windows\microsoft.net\framework\v4.0.30319\csc.exe NonPersistedMemoryMappedFileB.cs
После компиляции откройте командную строку и выполните NonPersistedMemoryMappedFileA.exe. После появления просьбы запустите NonPersistedMemoryMappedFileB.exe и нажмите Enter в окне NonPersistedMemoryMappedFileA.exe. После этого будет видно как приложение NonPersistedMemoryMappedFileB.exe выводит сообщения посылаемы ему из NonPersistedMemoryMappedFileA.exe.
Статьи по Теме
• Введение в Code Access Security в .NET Framework
•.NET Framework: Monitor a Directory for a New File and Use FTP Upload to Copy It
• Изменения в классах System.IO в .NET Framework 4.0