Возможно ли частично освободить динамически выделяемую память в системе POSIX?


У меня есть приложение C++, где мне иногда требуется большой буфер POD-типов (например, массив из 25 b illion float s), который должен храниться в памяти сразу в непрерывном блоке. Эта специфическая организация памяти обусловлена тем, что приложение использует некоторые API C, которые работают с данными. Поэтому другое расположение (например, список меньших блоков памяти, таких как std::deque uses) невозможно.

Приложение имеет алгоритм, который выполняется на массиве в потоковом режиме; подумайте что-то вроде этого:

std::vector<float> buf(<very_large_size>);
for (size_t i = 0; i < buf.size(); ++i) do_algorithm(buf[i]);
Этот конкретный алгоритм является завершением конвейера предыдущих шагов обработки, которые были применены к набору данных. Поэтому, как только мой алгоритм прошел над i-м элементом в массиве, приложение больше не нуждается в нем. Таким образом, теоретически я мог бы освободить эту память, чтобы уменьшить объем памяти моего приложения, когда оно пережевывает данные. Однако, делая что-то сродни realloc() (или a std::vector<T>::shrink_to_fit()) было бы неэффективно, потому что мое приложение должно было бы тратить свое время на копирование неиспользуемых данных в новое место во время перераспределения.

Мое приложение работает на POSIX-совместимых операционных системах (например, Linux, OS X). Существует ли какой-либо интерфейс, с помощью которого я мог бы попросить операционную систему освободить только определенную область от передней части блока памяти? Это казалось бы наиболее эффективным подходом, так как я мог бы просто уведомить диспетчер памяти, что, например, первые 2 ГБ блок памяти может быть восстановлен, как только я закончу с ним.

3   9  

3 ответа:

Возможно ли частично освободить динамически выделяемую память в системе POSIX?

Вы не можете сделать это с помощью malloc()/realloc()/free().

Тем не менее, вы можете сделать это полупортативным способом, используя mmap() и munmap(). Ключевой момент заключается в том, что если вы munmap() какую-то страницу, malloc() можете позже использовать эту страницу:
  • создайте анонимное отображение с помощью mmap();
  • затем вызовите munmap() для регионов, которые вам больше не нужны.

Переносимость вопросы следующие:

  • POSIX не определяет анонимные сопоставления. Некоторые системы предоставляют флаг MAP_ANONYMOUS или MAP_ANON. Другие системы предоставляют специальные файлы устройств, которые могут быть сопоставлены для этой цели. Linux предоставляет оба.
  • я не думаю, что POSIX гарантирует, что когда вы munmap() страницу, malloc() сможете ее использовать. Но я думаю, что это будет работать во всех системах, которые имеют mmap()/unmap().

Обновить

Если ваша область памяти настолько велика, что большинство страниц наверняка будет записано в swap, вы ничего не потеряете, используя сопоставления файлов вместо анонимных сопоставлений. Сопоставления файлов задаются в POSIX.

Если весь ваш буфер должен быть в памяти сразу, то вы, вероятно, не много выиграете от его частичного освобождения позже.

Главное в этом посте-не говорить вам делать то, что вы хотите, потому что ОС не будет без необходимости хранить память вашего приложения в оперативной памяти, если она на самом деле не нужна. В этом разница между "использованием резидентной памяти" и "использованием виртуальной памяти". "Резидент" - это то, что используется в настоящее время и в оперативной памяти, "виртуальный" - это общее использование памяти вашего заявления. И пока ваш раздел подкачки достаточно велик," виртуальная " память в значительной степени не является проблемой. [Я предполагаю, что ваша система не будет исчерпывать пространство виртуальной памяти, что верно в 64-разрядном приложении, пока вы не используете сотни терабайт виртуального пространства!]

Если вы все еще хотите сделать это, и хотите иметь некоторую разумную переносимость, я бы предложил построить "оболочку", которая ведет себя примерно как std::vector и выделяет куски некоторых мегабайт (или, возможно, несколько гигабайт) памяти за один раз, а затем что-то вроде:

 for (size_t i = 0; i < buf.size(); ++i) {
    do_algorithm(buf[i]);
    buf.done(i);
 }

Метод done просто проверяет, является ли значение if i (одним элементом) после окончания текущего буфера, и освобождает его. [Это должно быть встроенным красиво, и производить очень мало накладных расходов на среднем цикле - предполагая, что элементы на самом деле используются в линейном порядке, конечно].

Я был бы очень удивлен, если бы это принесло вам что-нибудь, если только do_algorithm(buf[i]) не займет довольно много времени (конечно, много секунд, вероятно, много минут или даже часов). И конечно, это поможет только в том случае, если у вас действительно есть что-то еще полезное для этой памяти. И даже тогда ОС будет восстанавливать память, которая не используется активно, заменяя ее на диск, Если системе не хватает памяти. Другими словами, если вы выделите 100 ГБ, заполните его, оставьте его сидеть, не касаясь, в конечном итоге все это будет на жестком диске, а не в оперативной памяти.

Далее, нет ничего необычного в том, что куча в приложение сохраняет освобожденную память, и что ОС не получает память обратно до тех пор, пока приложение не завершит работу - и, конечно, если освобождаются только части более крупного выделения, среда выполнения не освободит его, пока не будет освобожден весь блок. Итак, как было сказано в начале, я не уверен, насколько это действительно поможет вашему приложению.

Как и во всем, что касается "настройки" и "улучшения производительности", вам нужно измерить и сравнить эталон, и посмотреть, насколько он помогает.

Если вы можете обойтись без удобства std::vector (что в любом случае не даст вам много в этом случае, потому что вы никогда не захотите копировать / {1]} / переместить этого зверя в любом случае), вы можете сделать свою собственную обработку памяти. Запрашивайте у операционной системы целые страницы памяти (через mmap) и возвращать их по мере необходимости (используя munmap). Вы можете указать mmap через его аргумент fist и необязательный флаг MAP_FIXED, чтобы отобразить страницу по определенному адресу (который вы должны убедиться, что он не занят в противном случае, конечно конечно), так что вы можете создать область непрерывной памяти. Если вы заранее выделяете всю память, то это не проблема, и вы можете сделать это с помощью одного mmap и позволить операционной системе выбрать удобное место для его отображения. В конце концов, это то, что malloc делает внутренне. Для платформ, у которых нет sys/mman.h, нетрудно вернуться к использованию malloc, Если вы можете смириться с тем, что на этих платформах вы не вернете память раньше.

Я подозреваю, что если ваш размеры выделения всегда кратны размеру страницы, realloc будет достаточно умен, чтобы не копировать какие-либо данные. Вы должны попробовать это и посмотреть, работает ли это (или обратитесь к документации вашего malloc) на вашей конкретной целевой платформе.