Что такое файл отображаемый в память
A memory-mapped file contains the contents of a file in virtual memory. This mapping between a file and memory space enables an application, including multiple processes, to modify the file by reading and writing directly to the memory. You can use managed code to access memory-mapped files in the same way that native Windows functions access memory-mapped files, as described in Managing Memory-Mapped Files.
There are two types of memory-mapped files:
Persisted memory-mapped files
Persisted files are memory-mapped files that are associated with a source file on a disk. When the last process has finished working with the file, the data is saved to the source file on the disk. These memory-mapped files are suitable for working with extremely large source files.
Non-persisted memory-mapped files
Non-persisted files are memory-mapped files that are not associated with a file on a disk. When the last process has finished working with the file, the data is lost and the file is reclaimed by garbage collection. These files are suitable for creating shared memory for inter-process communications (IPC).
Processes, Views, and Managing Memory
Memory-mapped files can be shared across multiple processes. Processes can map to the same memory-mapped file by using a common name that is assigned by the process that created the file.
To work with a memory-mapped file, you must create a view of the entire memory-mapped file or a part of it. You can also create multiple views to the same part of the memory-mapped file, thereby creating concurrent memory. For two views to remain concurrent, they have to be created from the same memory-mapped file.
Multiple views may also be necessary if the file is greater than the size of the application's logical memory space available for memory mapping (2 GB on a 32-bit computer).
There are two types of views: stream access view and random access view. Use stream access views for sequential access to a file; this is recommended for non-persisted files and IPC. Random access views are preferred for working with persisted files.
Memory-mapped files are accessed through the operating system's memory manager, so the file is automatically partitioned into a number of pages and accessed as needed. You do not have to handle the memory management yourself.
The following illustration shows how multiple processes can have multiple and overlapping views to the same memory-mapped file at the same time.
The following image shows multiple and overlapped views to a memory-mapped file:
Программирование с использованием отображенных в память файлов
В следующей таблице содержатся инструкции по использованию объектов и элементов для отображенных в память файлов.
Programming with Memory-Mapped Files
The following table provides a guide for using memory-mapped file objects and their members.
До подобного момента надо ещё дожить, но однажды случается и такое. В один прекрасный день мне понадобилась БД с 1 триллионом записей. Причём, понадобилась на домашнем компьютере, где свободного места 700 гигабайт на двух дисках.
По счастью, моя БД не совсем обычная: размер записи всего 1 бит. В базе должны храниться данные о простых числах. Соответственно, вместо того, чтобы хранить сами числа, проще хранить один бит (1 - простое число, 0 - композитное). И тогда, чтобы хранить один триллион битов, нужно всего 116 ГБайт.
Однако сделав такой файл, мы получили только лишь хранилище, но не собственно БД. Нам нужен код, который будет записывать и считывать данные. Традиционный FileStream был отброшен сразу, по причине его медленности. Постоянное чередование Seek и чтения/записи по 1 байту даёт результат примерно в 100 раз худший, чем отображаемых на память файлы, опытом использования которых я и хочу поделиться в этой статье.
Выводы
Как следует из документации MSDN, класс MemoryMappedFile предназначен специально для работы с очень большими объёмами данных: "Memory-mapped files enable programmers to work with extremely large files". При использовании MemoryMappedViewAccessor прямой доступ к данным через ОПФ работает значительно быстрее, чем FileStream .
В то же время, при использовании описанных классов нужно помнить, что скорость записи данных на диск при использовании MemoryMappedViewAccessor зависит не только от объёма изменённых данных, но и от расстояния между ними.
В этой статье я хотел бы рассказать о такой замечательной штуке, как файлы, отображаемые в память(memory-mapped files, далее — MMF).
Иногда их использование может дать довольно таки существенный прирост производительности по сравнению с обычной буферизированной работой с файлами.
Это механизм, который позволяет отображать файлы на участок памяти. Таким образом, при чтении данных из неё, производится считывание соответствующих байт из файла. С записью аналогично.
«Клёво, конечно, но что это даёт?» — спросите вы. Поясню на примере.
Допустим, перед нами стоит задача обработки большого файла(несколько десятков или даже сотен мегабайт). Казалось бы, задача тривиальна — открываем файл, поблочно копируем из него в память, обрабатываем. Что при этом происходит. Каждый блок копируется во временный кэш, затем из него в нашу память. И так с каждым блоком. Налицо неоптимальное расходование памяти под кэш + куча операций копирования. Что же делать?
Тут-то нам на помощь и приходит механизм MMF. Когда мы обращаемся к памяти, в которую отображен файл, данные загружаются с диска в кэш(если их там ещё нет), затем делается отображение кэша в адресное пространство нашей программы. Если эти данные удаляются — отображение отменяется. Таким образом, мы избавляемся от операции копирования из кэша в буфер. Кроме того, нам не нужно париться по поводу оптимизации работы с диском — всю грязную работу берёт на себя ядро ОС.
В своё время я проводил эксперимент. Замерял с помощью quantify скорость работы программы, которая буферизировано копирует большой файл размером 500 мб в другой файл. И скорость работы программы, которая делает то же, но с помощью MMF. Так вот вторая работает быстрее почти на 30% (в Solaris, в других ОС результат может отличаться). Согласитесь, неплохо.
Чтобы воспользоваться этой возможностью, мы должны сообщить ядру о нашем желании отобразить файл в память. Делается это с помощью функции mmap().
Она возвращает адрес начала участка отображаемой памяти или MAP_FAILED в случае неудачи.
Первый аргумент — желаемый адрес начала участка отбраженной памяти. Не знаю, когда это может пригодится. Передаём 0 — тогда ядро само выберет этот адрес.
len — количество байт, которое нужно отобразить в память.
prot — число, определяющее степень защищённости отображенного участка памяти(только чтение, только запись, исполнение, область недоступна). Обычные значения — PROT_READ, PROT_WRITE (можно кобминировать через ИЛИ). Не буду на этом останавливаться — подробнее читайте в манах. Отмечу лишь, что защищённость памяти не установится ниже, чем права, с которыми открыт файл.
flag — описывает атрибуты области. Обычное значение — MAP_SHARED. По поводу остальных — курите маны. Но замечу, что использование MAP_FIXED понижает переносимость приложения, т.к. его подержка является необязательной в POSIX-системах.
filedes — как вы уже догались — дескриптор файла, который нужно отобразить.
off — смещение отображенного участка от начала файла.
Важное замечание. Если вы планируете использовать MMF для записи в файл, перед маппингом необходимо установить конечный размер файла не меньше, чем размер отображенной памяти! Иначе нарвётесь на SIGBUS.
Ниже приведён пример(честно стырен из замечательной книжки «Unix. Профессиональное программирование») программы, которая копирует файл с использованием MMF.
Вот вобщем-то и всё. Надеюсь, эта статья была полезной. С удовольствием приму конструктивную критику.
Продолжаем публикацию книги о программирование на языке ассемблера (GAS) в операционной системе Linux (x86-64) . Сегодня продолжаем рассматривать вопросы памяти.
Отображаемые на память файлы
Отображаемые на память файлы (ОПФ) работают за счёт аппарата виртуальной памяти. По принципу работы они аналогичны файлу подкачки Windows (pagefile.sys) с той разницей, что доступ к последнему есть только у самой ОС, а ОПФ доступен владельцу и может разделяться между процессами.
Работают эти классы так. MemoryMappedFile открывает СПФ, подключая его к адресному пространству приложения. Для этого у него есть методы CreateFromFile , CreateNew , CreateOrOpen , OpenExisting . Открытие СПФ не влечёт значимых расходов памяти, т.к. распределяется виртуальная, а не физическая память.
Чтобы получить доступ к содержимому файла, необходимо использовать один из двух классов, которые его предоставляют. Прямой доступ предоставляется классом MemoryMappedViewAccessor , а последовательный – классом MemoryMappedViewStream .
Для своего приложения я использовал прямой доступ, соответственно основная часть этой статьи посвящена особенностям MemoryMappedViewAccessor , главным образом, выбору между затратами памяти или времени.
Этот выбор стал для меня критическим, когда я занялся заполнением БД при помощи алгоритма, известного как решето Эратосфена. Это крайне простой алгоритм. Исходный код приводится для того, чтобы показать, в каком окружении работает ОПФ и в каком месте делается выбор между расходом памяти или времени.
Функция FindPrimes реализует решето Эратосфена. Она устанавливает, что числа, кратные простым, сами простыми не являются, и последовательно исключает их.
В данном фрагменте Registry – это собственно БД на основе MemoryMappedViewAccessor . Её методы: Contains() – проверяет, является ли число простым; SetState() – устанавливает признак простого ( True ) или композитного ( False ) числа.
Метод Flush() сбрасывает сделанные изменения на диск ( MemoryMappedViewAccessor.Flush() ), избавляется от аксессора ( MemoryMappedViewAccessor.Dispose() ) и создаёт вместо него новый. Использование или неиспользование этого метода и определяет, что будет расходоваться сильнее – память или время.
Параметр interval (в моём случае 100 млн.) подобран таким, чтобы периодический отчёт о прогрессе (в коде выше отсутствует) происходил примерно раз в 5 секунд. Будучи запущенной, программа показала следующее поведение.
Изначально вызова Registry.Flush() в программе не было. В диспетчере задач программа занимает не более 3 МБ, но общий расход памяти очень быстро доходит до 99%. При этом, время отклика компьютера значимо не растёт, новые приложения запускаются, из чего можно сделать вывод, что Windows отдаёт моему СПФ всю доступную память, но не в ущерб другим приложениям.
Насколько быстро работает ОПФ? 100 млн. вызовов Registry.SetState() занимает примерно 4,5 - 5 секунд, таким образом, первая итерация по i=3 занимает в общей сложности 4 часа. Мой СПФ расположен на HDD, видимо, на SSD затраты времени были бы меньше.
Можно заметить, что с ростом i вложенный цикл выполняется всё быстрее и быстрее, т.к. как шагаем всё более длинными шагами. При i=3 он занимает 4 часа, для i=5 3 часа, для i=7 2 часа с хвостом и т.д. (в данный момент ещё работает). Медлительный вначале, к концу алгоритм Эратосфена разгоняется как ракета.
Дальнейшая работа над программой была направлена на снижение расхода памяти. Для этого в конце интервала был добавлен вызов Registry.Flush() . Этот вызов возымел нужное действие, расход памяти растёт во вложенном цикле, но при вызове Flush() падает до исходного уровня. Таким образом, задача казалась решённой.
Начиная с i=3 , вызов Flush() добавлял около 700 мс к 4,5 - 5 секундам, что казалось приемлемой ценой за экономию памяти. Однако, это время начало быстро расти с ростом i : при i=5 850 мс, при i=7 1 сек и так далее. При i=823 время на Flush() было уже порядка 40 секунд! В результате, рост скорости алгоритма был полностью съеден увеличивающимися затратами времени на Flush() .
Этот результат показывает, что при отработке Flush() , MemoryMappedViewAccessor пишет на диск весь диапазон между собственно изменёнными байтами. И, поскольку с ростом i, мы "шагаем всё шире", этот диапазон становится больше.
Данная особенность MemoryMappedViewAccessor заставила отказаться от сброса аксессора и экономии памяти. К сожалению, упомянутые в статье классы не имеют каких-либо опций настройки использования памяти.
Файлы, отображаемые в память на ассемблере в Linux 64
Динамическая память очень удобна в плане работы с файлами. Она предоставляет технологию отображения файла в память, позволяющую упростить и ускорить обработку данных. Дело в том, что область памяти, которую мы кучей (см. предыдущий параграф 5.3 ) делится еще на две части. Часть, ближайшая к стеку может быть использована для отображения файлов. Другая часть, как мы писали, может динамически выделяться программе (см. Рисунок 1).
В данном параграфе мы рассмотрим технологию использования файлов, отображаемых в память. Суть технологии заключается в следующем:
1. Открывается файл известным уже нам способом.
2. Для его отображения (или отображения его части) используется функция mmap (номер 9). После чего открытый файл может быть закрыт.
3. После отображения программа получает адрес начала области отображения. Далее мы можем работать с этой областью, как обычной областью памяти. Т.е. читать и писать ее командами процессора.
4. По окончанию работы с отображенным файлом он закрывается с помощью функции munmap (номер 11).
Работать с файлами, отображенными в память, чрезвычайно удобно и быстро. Описанная выше технология представлена в программе из листинга 52.
Пояснение к программе из листинга 52.
Поскольку технология работы с файлами, отображенными в память, выше была описана, а программа в листинге 52 прокомментирована подробно, разберем только используемые функции mmap и munmap .
Программа может быть откомпилирована с помощью последовательности командами
as --64 l48.s -o l48.o
ld -s l48.o -o l48
Функция mmap.
1-й параметр. Начальный адрес, куда предлагается отобразить файл. Обычно полагается 0, что означает, что система сама должна его выбрать.
2-й параметр. Задает длину отображаемого файла или его участка. В нашей программе мы взяли длину файла.
3-й параметр. Определяет уровень доступа к отображенному файлу. В нашей программе выбрано PROT_WRITE|PROT_READ , что означает, доступ будет и для чтения и для записи.
4-й параметр. Определяет возможность доступа к участку других процессов. У нас взято MAP_SHARED , т.е. доступ разрешен. Впрочем этот факт нами никак не используется.
5-й параметр. Дескриптор открытого нами файла. Как уже было сказано выше, после отображения, файл закрывается.
6-й параметр. Указывает смещение в файле, откуда будет произведено отображение. В нашем случае, мы отображаем весь файл, значит этот параметр будет равен 0.
После отображения программа просматривает отображенный файл и заменяет буквы i на цифру 1.
Функция munmap.
Функция имеет всего два параметр. Первый параметр адрес начала отображенного файла, второй параметр — длина отображения. Адрес, откуда начинается отображение, получается из функции mmap . Длина же отображения нами была задана равной длине открытого файла. В результате область отображения освобождается, а данные сбрасываются в указанный файл.
На сегодня все. Пока! Подписываемся на мой канал Old Programmer и ставьте "лайки". А я продолжаю заниматься книгой Ассемблер для Linux 64.
Отображаемый в память файл содержит содержимое файла в виртуальной памяти. Отображение файла в области памяти позволяет приложению, содержащему несколько процессов, взаимодействовать с файлом путем чтения этой памяти и записи в нее. Вы можете использовать управляемый код для доступа к сопоставленным в памяти файлам тем же способом, что и собственные функции Windows. Описание этого механизма можно найти на странице Управление сопоставленными в памяти файлами.
Есть два типа отображенных в память файлов:
Постоянные отображенные в память файлы.
В контексте отображения в память постоянными называются файлы, сопоставленные с исходным файлом на диске. Когда последний процесс завершит работу с таким файлом, все данные сохраняются в исходный файл на диске. Такие отображенные в память файлы удобны для работы с очень большими исходными файлами.
Непостоянные отображенные в память файлы.
Непостоянными называются отображенные в память файлы, которые не сопоставлены с файлом на диске. Когда последний процесс закончит работу с таким файлом, данные не сохраняются, а файл удаляется при сборке мусора. Такие файлы позволяют создать общую область памяти для межпроцессного взаимодействия (IPC).
Процессы, представления и управление памятью
Отображенные в память файлы можно сделать общими для нескольких процессов. Процессы могут обращаться к одному отображенному в память файлу по единому имени, которое назначается процессом, создающим этот файл.
Для работы с отображенным в память файлом следует создать представление всего файла или его части. Также вы можете создать несколько представлений для одной части отображенного в память файла, фактически применяя одновременно используемую память. Чтобы два представления оставались согласованными, их нужно создавать из одного отображенного в память файла.
Несколько представлений потребуются еще и в том случае, если размер файла превышает размер пространства логической памяти, доступной приложению для сопоставления с памятью (например, 2 ГБ на 32-разрядном компьютере).
Есть два типа представлений: представление потокового доступа и представление произвольного доступа. Представления потокового доступа удобны для последовательного доступа к файлу (чаще всего это непостоянные файлы и IPC). Представления произвольного доступа предпочтительны для работы с постоянными файлами.
Доступ к сопоставленным с памятью файлам осуществляется через диспетчер памяти операционной системы, который автоматически разделяет файл на несколько страниц и предоставляет их по мере необходимости. Вам не придется самостоятельно осуществлять управление памятью.
На следующем изображении показано, как несколько процессов могут одновременно использовать несколько перекрывающихся представлений для одного отображенного в память файла:
На следующем рисунке показано несколько перекрывающихся представлений для отображенного в память файла:
Читайте также: