Затраты на производительность при хранении mmap огромного файла в памяти (около 400 МБ) по сравнению с mmap и munmap в несколько раз

Я столкнулся с проблемой проектирования, связанной с отображением файла в оперативной памяти (созданного в папке tmpfs). Размер файла в оперативной памяти составляет около 400 МБ, что в худшем случае составляет около 100 тысяч страниц (если только ядро ​​не решит использовать прозрачные огромные страницы по 2 МБ каждая, в этом случае их количество упадет до 200).

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

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

Использование вами «отображения файла в оперативной памяти» сбивает с толку. Из вашей формулировки следует, что файл изначально существует на tmpfs или виртуальном диске. Где этот файл хранится изначально, в фс на носителе или в tmpfs или???

sawdust 30.04.2024 05:47

@sawdust Он существует в папке tmpfs. Я добавил уточнение в вопрос.

ABu 30.04.2024 10:00

Tmpfs использует кеш страниц, о котором @MarcoBonelli упоминает в своем ответе. Но этот ответ, похоже, предполагает, что файл действительно находится в запоминающем устройстве, например. HDD или SSD. Таким образом, некоторые части этого ответа не применимы к вашему особому случаю.

sawdust 30.04.2024 17:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это сильно зависит от конкретной среды, в которой работает ваша программа.

Теоретически ядро ​​является умным и поддерживает кеш, называемый «кешем страниц», в котором недавно сопоставленные страницы технически могут оставаться нетронутыми в течение неопределенного времени после того, как их отобразили из программ пользовательского пространства. Они по-прежнему занимают память, но могут быть удалены в любой момент и записаны обратно на диск, когда эта память понадобится. Если вы откроете 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 только для изменения прав доступа (с чтения на чтение | запись), и там я увидел упоминание о заполнении всех записей страницы для изменения разрешений, как если бы это было так. кое-что, что следует принять во внимание, что заставило меня задать этот вопрос.

ABu 30.04.2024 01:06

Интересно, вызывает ли mmaping файла в оперативной памяти ошибки страницы (сначала чтение или запись) в случае, если виртуальные страницы еще не назначены, для выполнения привязок к соответствующим записям страничного кэша или чего-то еще, или ядро ​​оптимизирует это с помощью заполнение таблицы страниц в ее «окончательной форме» внутри вызова mmap.

ABu 30.04.2024 01:10

@ABu Я вижу, что время, потраченное ядром на заполнение записей таблицы страниц объемом 400 МБ, весьма существенно, если вы делаете это «десятки раз с интервалом в миллисекунды». Вы можете проверить это, написав простую программу, которая выполняет только эту операцию, и запустив ее с помощью таких инструментов, как perf, чтобы проверить количество затраченного ядра и времени пользователя.

Marco Bonelli 30.04.2024 01:14

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

Marco Bonelli 30.04.2024 01:14

Работает ли MAP_POPULATE в режиме блокировки? (mmap не возвращается, пока все страницы не будут предварительно загружены?) Или это больше похоже на «предварительную выборку», которая происходит асинхронно (в том смысле, что ядро ​​начинает работать над ней «сейчас», но не блокирующим образом)?

ABu 30.04.2024 01:25

@ABu хороший вопрос. Это блокирует. Технически есть MAP_NONBLOCK, чтобы сделать его неблокирующим, раньше это вызывало неблокирующее упреждающее чтение, как вы говорите, но в настоящее время это просто заставляет MAP_POPULATE действовать как неактивный.

Marco Bonelli 30.04.2024 01:42

Другие вопросы по теме