Файл Mmap, чем открыть

Обновлено: 02.07.2024

Когда я спрашиваю своих коллег, почему mmap работает быстрее, чем системные вызовы, ответ неизбежно звучит так: «накладные расходы на системные вызовы»: стоимость пересечения границы между пользовательским пространством и ядром. Оказывается, эти накладные расходы имеют больше нюансов, чем я думал, поэтому давайте заглянем внутрь, чтобы понять разницу в производительности.

Предыстория (пропустите, если вы разбираетесь в ОС):

Системные вызовы. Системный вызов — это специальная функция, позволяющая выполнять перекрестную защиту доменов. Когда программа выполняется в пользовательском режиме (непривилегированный домен защиты), ей не разрешается делать то, что разрешено коду, выполняющемуся в режиме ядра (привилегированный домен защиты). Например, программа, работающая в пользовательском пространстве, обычно не может читать файлы без помощи ядра. Когда пользовательская программа запрашивает службу у операционной системы, система защищает себя от вредоносных программ или программ с ошибками с помощью системных вызовов. Системный вызов выполняет специальную аппаратную инструкцию, часто называемую «trap», которая передает управление ядру. Затем ядро ​​может решить, будет ли оно выполнять запрос.

Хотя эта защита очень полезна, она имеет свою цену. Когда мы переходим из пользовательского пространства в ядро, нам приходится сохранять аппаратные регистры, потому что они могут понадобиться ядру. Кроме того, поскольку напрямую разыменовывать указатели пользовательского уровня небезопасно (а что, если они нулевые — это приведет к сбою ядра!), данные, на которые ссылаются эти указатели, должны быть скопированы в ядро.

Когда мы возвращаемся из системного вызова, мы должны повторить последовательность в обратном порядке: скопировать все данные, запрошенные пользователем (поскольку мы не можем просто передать указатели пользовательских программ в память ядра), восстановить регистры и перейти в пользовательский режим.

Ошибки страницы. Операционная система и оборудование вместе преобразуют адреса, записанные в исполняемом файле вашей программы (они называются виртуальными адресами), в адреса в фактической физической памяти (физические адреса). ). Для компилятора было бы довольно неудобно генерировать физические адреса напрямую, потому что он не знает, на какой машине вы можете запустить свою программу, сколько у нее памяти и какие другие программы могут использовать физическую память во время выполнения вашей программы. Отсюда необходимость в этой трансляции виртуального адреса в физический. Переводы или сопоставления настраиваются в таблице страниц вашей программы. Когда ваша программа начинает работать, ни одно из этих сопоставлений не настроено. Таким образом, когда ваша программа пытается получить доступ к виртуальному адресу, она генерирует ошибку страницы, которая сигнализирует ядру о необходимости настройки сопоставления. Ядро уведомляется о том, что ему необходимо обработать отказ страницы через ловушку, таким образом, это немного похоже на системный вызов. Разница в том, что системный вызов является явным, а ошибка страницы — неявной.

Кэш буфера. Буферный кеш — это часть памяти ядра, которая используется для хранения фрагментов файлов, к которым недавно обращались (эти фрагменты называются блоками или страницами). Когда пользовательская программа запрашивает чтение файла, страница из файла (обычно) сначала помещается в буферный кеш. Затем данные копируются из буферного кэша в предоставленный пользователем буфер во время возврата из системного вызова.

Мап. Mmap означает файлы отображенные в память. Это способ чтения и записи файлов без вызова системных вызовов. Операционная система резервирует часть виртуальных адресов программы для непосредственного «сопоставления» части в файле. Таким образом, если программа считывает данные из этой части адресного пространства, она получит данные, находящиеся в соответствующей части файла. Если эта часть файла окажется в буферном кеше, виртуальные адреса сопоставленного фрагмента будут просто сопоставлены с физическими адресами соответствующих страниц буферного кеша при первом доступе, и никакие системные вызовы или другие ловушки не будут вызываться. позже. Если данные файла не находятся в буферном кеше, доступ к отображаемой области приведет к ошибке страницы, что побудит ядро ​​перейти к получению соответствующих данных с диска.

Почему mmap должен быть быстрее

Начнем с формулировки гипотезы. Почему мы ожидаем, что mmap будет быстрее? Есть две очевидные причины. Во-первых, он не требует явного пересечения защитных доменов, хотя неявное пересечение по-прежнему происходит, когда возникают ошибки страниц. Тем не менее, если к заданному диапазону в файле обращаются более одного раза, есть вероятность, что мы не столкнемся с ошибками страницы после первого доступа. Однако в моих экспериментах этого не происходило, поэтому я ожидал, что каждый раз, когда буду читать новый блок файла, будет срабатывать ошибка страницы.

Во-вторых, если приложение написано таким образом, что оно может обращаться к данным непосредственно в отображаемой области, нам не нужно выполнять копирование памяти. Однако в своих экспериментах мне было интересно измерить сценарий, в котором приложение имеет отдельный целевой буфер для данных, которые оно считывает.Таким образом, даже несмотря на то, что файл сопоставлен, приложение все равно скопирует данные из сопоставленной области в целевой буфер.

Поэтому в моей экспериментальной среде я ожидал, что mmap будет немного быстрее, чем системные вызовы, потому что я думал, что код для обработки ошибок страниц будет немного более упорядоченным, чем код для системных вызовов.

Эксперимент

Я организовал свой эксперимент следующим образом. Я создаю файл размером 4 ГБ, а затем читаю его последовательно или случайно, используя размер блока 4 КБ, 8 КБ или 16 КБ. Я прочитал файл с помощью системного вызова чтения или mmap. В случае mmap я не просто обращаюсь к отображаемой области напрямую, но копирую данные из отображаемой области в отдельный «целевой» буфер (см. сообщение в блоге, описывающее мой целевой вариант использования понять, почему я так делаю). Таким образом, в обоих экспериментах мы копируем данные из кэша буфера ядра в буфер назначения пользователя, но в случае mmap мы делаем это с помощью ошибок страниц, а в случае системных вызовов мы делаем это с помощью чтение системного вызова.

Я запускаю эти тесты с использованием либо холодного буферного кеша, что означает, что файл там не кэшируется, либо теплого буферного кеша, что означает, что файл находится в память ядра. Носитель данных — это твердотельный накопитель, который вы можете ожидать от типичного сервера. Все операции чтения выполняются в одном потоке. Исходный код моего теста находится здесь.

Результаты

На следующих диаграммах показана производительность теста чтения для последовательного/теплого, последовательного/холодного, случайного/теплого и случайные/холодные запуски.

Файл с отображением памяти содержит содержимое файла в виртуальной памяти. Это сопоставление между файлом и областью памяти позволяет приложению, включая несколько процессов, изменять файл, читая и записывая непосредственно в память. Вы можете использовать управляемый код для доступа к файлам, отображаемым в памяти, так же, как собственные функции Windows обращаются к файлам, отображаемым в памяти, как описано в разделе Управление файлами, отображаемыми в памяти.

Существует два типа отображаемых в память файлов:

Сохраняемые файлы, отображенные в памяти

Сохраняемые файлы — это отображаемые в память файлы, связанные с исходным файлом на диске. Когда последний процесс закончил работу с файлом, данные сохраняются в исходный файл на диске. Эти отображаемые в память файлы подходят для работы с очень большими исходными файлами.

Не сохраняемые файлы с отображением в памяти

Несохраняемые файлы — это отображаемые в памяти файлы, не связанные с файлом на диске. Когда последний процесс завершает работу с файлом, данные теряются, а файл утилизируется сборщиком мусора. Эти файлы подходят для создания общей памяти для межпроцессного взаимодействия (IPC).

Процессы, представления и управление памятью

Файлы, отображаемые в память, могут совместно использоваться несколькими процессами. Процессы могут сопоставляться с одним и тем же отображаемым в память файлом, используя общее имя, которое назначается процессом, создавшим файл.

Для работы с отображенным в память файлом необходимо создать представление всего отображаемого в памяти файла или его части. Вы также можете создать несколько представлений для одной и той же части отображаемого в память файла, тем самым создавая параллельную память. Чтобы два представления оставались параллельными, они должны быть созданы из одного и того же файла с отображением в памяти.

Несколько представлений также могут быть необходимы, если размер файла превышает размер логической памяти приложения, доступной для сопоставления памяти (2 ГБ на 32-разрядном компьютере).

Существует два типа представлений: представление потокового доступа и представление произвольного доступа. Используйте представления потокового доступа для последовательного доступа к файлу; это рекомендуется для несохраняемых файлов и IPC. Представления произвольного доступа предпочтительны для работы с постоянными файлами.

Доступ к файлам, отображаемым в памяти, осуществляется через диспетчер памяти операционной системы, поэтому файл автоматически разбивается на несколько страниц и используется по мере необходимости. Вам не нужно самостоятельно управлять памятью.

На следующем рисунке показано, как несколько процессов могут одновременно иметь несколько перекрывающихся представлений одного и того же файла, отображаемого в память.

На следующем изображении показаны несколько и перекрывающиеся представления файла с отображением в памяти:

Снимок экрана, на котором показаны представления файла, отображенного в памяти.

Программирование с помощью файлов, отображенных в памяти

В следующей таблице приведены рекомендации по использованию отображаемых в памяти файловых объектов и их членов.

Читайте также: