Я столкнулся с проблемой проектирования, связанной с отображением файла в оперативной памяти (созданного в папке tmpfs
). Размер файла в оперативной памяти составляет около 400 МБ, что в худшем случае составляет около 100 тысяч страниц (если только ядро не решит использовать прозрачные огромные страницы по 2 МБ каждая, в этом случае их количество упадет до 200).
Я не знаю, оставить ли mmap открытым или mmap
его, затем munmap
и снова mmap
, когда он мне понадобится позже. Файл будет использоваться при лавинах, как будто его можно использовать несколько раз в течение нескольких минут, а затем больше не использовать в течение нескольких часов.
На самом деле меня не особо беспокоит производительность, но мне любопытно узнать, какова стоимость производительности при отображении и преобразовании большого раздела, состоящего из тысяч страниц, десятки раз с интервалами в несколько миллисекунд. Какую работу должно выполнить ядро для сопоставления, установки разрешений каждой страницы и т. д.?
@sawdust Он существует в папке tmpfs. Я добавил уточнение в вопрос.
Tmpfs использует кеш страниц, о котором @MarcoBonelli упоминает в своем ответе. Но этот ответ, похоже, предполагает, что файл действительно находится в запоминающем устройстве, например. HDD или SSD. Таким образом, некоторые части этого ответа не применимы к вашему особому случаю.
Это сильно зависит от конкретной среды, в которой работает ваша программа.
Теоретически ядро является умным и поддерживает кеш, называемый «кешем страниц», в котором недавно сопоставленные страницы технически могут оставаться нетронутыми в течение неопределенного времени после того, как их отобразили из программ пользовательского пространства. Они по-прежнему занимают память, но могут быть удалены в любой момент и записаны обратно на диск, когда эта память понадобится. Если вы откроете htop
или аналогичные инструменты, вы увидите, сколько вашей памяти используется для кеша:
Команда free
также показывает эту информацию в текстовом виде:
$ free -h
total used free shared buff/cache available
Mem: 31Gi 5.4Gi 22Gi 164Mi 4.0Gi 25Gi
Swap: 31Gi 0B 31Gi
Когда вы сопоставляете файл, если это явно не запрошено, он обычно не считывается в память немедленно, а по требованию при доступе к каждой странице. Первый доступ к новым сопоставлениям генерирует ошибку страницы, и только затем ядро загружает фактические данные в память. Как только это будет сделано, страницы также будут кэшироваться в кэше страниц.
Следовательно, если вы напишете программу, которая отображает большой файл, использует его, отобразяет, а затем снова отображает, чтобы повторно использовать его за короткий промежуток времени, во второй раз страницы наверняка уже будут на странице. кэш. В этом случае единственная работа, которую нужно будет выполнить ядру, — это заполнить соответствующие записи таблицы страниц, что происходит быстро.
Однако чем больше времени проходит между отключением и повторным сопоставлением, тем выше вероятность того, что страницы будут удалены из кэша страниц и записаны на диск. Это связано с тем, что практически любое сопоставление на основе файлов использует страничный кеш, а память (ОЗУ) обычно ограничена. Программы, которые работают параллельно с вашей, также постоянно запрашивают новую память, и ядру, возможно, придется вернуть часть ее, отбрасывая страницы из страничного кэша.
В конце концов, будет ли описываемая вами операция (постоянное отключение и повторное сопоставление) быстрой или медленной, на самом деле зависит от того, сколько памяти у вас доступно и насколько загружена система в данный момент. Что определенно является быстрым, так это всегда сохранять сопоставленный файл и, возможно, блокировать его в оперативной памяти (mlock(2)), чтобы он не был выгружен. Конечно, сможете вы это сделать или нет, зависит от конкретного случая, но чисто с точки зрения производительности это будет лучший вариант.
Поведение ядра в отношении кешированной памяти также можно изменить с помощью нескольких кнопок sysctl, в частности:
/proc/sys/vm/dirty_background_bytes
/proc/sys/vm/dirty_background_ratio
/proc/sys/vm/dirty_bytes
/proc/sys/vm/dirty_expire_centisecs
/proc/sys/vm/dirty_ratio
См. эту страницу документации, где они описаны для получения дополнительной информации.
Мое главное «беспокойство» заключалось в стоимости заполнения таблицы страниц тысячами записей каждый раз. Я знал о поведении страничного кеша. Я читал в документации о стоимости переназначения mmap только для изменения прав доступа (с чтения на чтение | запись), и там я увидел упоминание о заполнении всех записей страницы для изменения разрешений, как если бы это было так. кое-что, что следует принять во внимание, что заставило меня задать этот вопрос.
Интересно, вызывает ли mmaping файла в оперативной памяти ошибки страницы (сначала чтение или запись) в случае, если виртуальные страницы еще не назначены, для выполнения привязок к соответствующим записям страничного кэша или чего-то еще, или ядро оптимизирует это с помощью заполнение таблицы страниц в ее «окончательной форме» внутри вызова mmap.
@ABu Я вижу, что время, потраченное ядром на заполнение записей таблицы страниц объемом 400 МБ, весьма существенно, если вы делаете это «десятки раз с интервалом в миллисекунды». Вы можете проверить это, написав простую программу, которая выполняет только эту операцию, и запустив ее с помощью таких инструментов, как perf
, чтобы проверить количество затраченного ядра и времени пользователя.
@ABu Что касается второй части: первоначальный системный вызов mmap
вставляет «поддельные» записи в таблицу страниц только для того, чтобы зарезервировать область виртуальной памяти, именно последующие ошибки страниц заставляют ядро изменять записи в их окончательную форму. Однако вы можете попросить сделать это немедленно mmap
, используя флаг MAP_POPULATE
.
Работает ли MAP_POPULATE
в режиме блокировки? (mmap
не возвращается, пока все страницы не будут предварительно загружены?) Или это больше похоже на «предварительную выборку», которая происходит асинхронно (в том смысле, что ядро начинает работать над ней «сейчас», но не блокирующим образом)?
@ABu хороший вопрос. Это блокирует. Технически есть MAP_NONBLOCK
, чтобы сделать его неблокирующим, раньше это вызывало неблокирующее упреждающее чтение, как вы говорите, но в настоящее время это просто заставляет MAP_POPULATE
действовать как неактивный.
Использование вами «отображения файла в оперативной памяти» сбивает с толку. Из вашей формулировки следует, что файл изначально существует на tmpfs или виртуальном диске. Где этот файл хранится изначально, в фс на носителе или в tmpfs или???