Вернут ли реализации malloc свободную память обратно в систему?

У меня долгоживущее приложение с частым выделением-освобождением памяти. Будет ли любая реализация malloc возвращать освобожденную память обратно в систему?

Каково в этом отношении поведение:

  • ptmalloc 1, 2 (по умолчанию для glibc) или 3
  • dlmalloc
  • tcmalloc (Google Malloc с резьбой)
  • Солярис 10-11 по умолчанию malloc и mtmalloc
  • FreeBSD 8 по умолчанию malloc (jemalloc)
  • Клад Маллок?

Обновить

Если у меня есть приложение, чье потребление памяти может сильно отличаться в дневное и ночное время (например), могу ли я заставить какой-либо из malloc вернуть освобожденную память в систему?

Без такого возврата освобожденная память будет многократно выгружаться, но такая память содержит только мусор.

7 ответов

Решение

Следующий анализ относится только к glibc (основан на алгоритме ptmalloc2). Существуют определенные опции, которые кажутся полезными для возврата освобожденной памяти обратно в систему:

  1. mallopt () (определяется в malloc.h) предоставляет возможность установить пороговое значение обрезки, используя один из параметров M_TRIM_THRESHOLD, это указывает минимальный объем свободной памяти (в байтах), разрешенный в верхней части сегмента данных. Если сумма падает ниже этого порога, glibc вызывает brk() вернуть память ядру.

    Значение по умолчанию M_TRIM_THRESHOLD в Linux установлено 128 КБ, установка меньшего значения может сэкономить место.

    Такое же поведение может быть достигнуто путем установки порогового значения обрезки в переменной среды MALLOC_TRIM_THRESHOLD_, без изменений источника абсолютно.

    Тем не менее, предварительные тестовые программы запускаются с использованием M_TRIM_THRESHOLD показал, что даже если память, выделенная malloc, возвращается в систему, оставшаяся часть фактической порции памяти (арена) первоначально запрашивается через brk() как правило, сохраняется.

  2. Можно обрезать арену памяти и вернуть любую неиспользуемую память обратно в систему, вызвав malloc_trim(pad) (определено в malloc.h). Эта функция изменяет размер сегмента данных, оставляя как минимум pad байты в конце и сбой, если можно освободить байты размером менее одной страницы. Размер сегмента всегда кратен одной странице, что составляет 4096 байт на i386.

    Реализация этого измененного поведения free() с помощью malloc_trim может быть сделано с использованием функциональности malloc hook. Это не потребует никаких изменений исходного кода в базовой библиотеке glibc.

  3. с помощью madvise() Системный вызов внутри бесплатной реализации glibc.

Большинство реализаций не беспокоит выявление тех (относительно редких) случаев, когда целые "блоки" (любого размера, подходящего для ОС) были освобождены и могли быть возвращены, но, конечно, есть исключения. Например, и я цитирую со страницы википедии в OpenBSD:

По вызову free, память освобождается и отображается из адресного пространства процесса с помощью munmap. Эта система разработана для повышения безопасности благодаря использованию возможностей рандомизации макета адресного пространства и функций пробелов на страницах, реализованных в составе OpenBSD. mmap системный вызов и обнаружение ошибок использования после освобождения - поскольку большое выделение памяти полностью освобождается после освобождения, дальнейшее использование вызывает ошибку сегментации и завершение программы.

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

Зная это, когда я кодирую долго работающую систему, для которой требуется большое количество памяти, я всегда стараюсь fork процесс: потом родитель просто ждет результатов от потомка [[как правило, по конвейеру]], потомок выполняет вычисления (включая распределение памяти), возвращает результаты [[по указанному каналу]], а затем завершается. Таким образом, мой длительный процесс не будет бесполезно перегружать память в течение долгого времени между случайными "скачками" ее спроса на память. Другие альтернативные стратегии включают переключение на специальный распределитель памяти для таких особых требований (C++ делает это достаточно просто, хотя языки с виртуальными машинами под ними, такие как Java и Python, обычно этого не делают).

У меня была похожая проблема в моем приложении, после некоторого исследования я заметил, что по какой-то причине glibc не возвращает память системе, когда выделенные объекты малы (в моем случае менее 120 байт).
Посмотрите на этот код:

#include <list>
#include <malloc.h>

template<size_t s> class x{char x[s];};

int main(int argc,char** argv){
    typedef x<100> X;

    std::list<X> lx;
    for(size_t i = 0; i < 500000;++i){
        lx.push_back(X());
    }

    lx.clear();
    malloc_stats();

    return 0;
}

Выход программы:

Arena 0:
system bytes     =   64069632
in use bytes     =          0
Total (incl. mmap):
system bytes     =   64069632
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

около 64 МБ не возвращается в систему. Когда я изменил typedef на:typedef x<110> X; Вывод программы выглядит так:

Arena 0:
system bytes     =     135168
in use bytes     =          0
Total (incl. mmap):
system bytes     =     135168
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

почти вся память была освобождена. Я также заметил, что с помощью malloc_trim(0) в любом случае освободил память для системы.
Вот вывод после добавления malloc_trim к коду выше:

Arena 0:
system bytes     =       4096
in use bytes     =          0
Total (incl. mmap):
system bytes     =       4096
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

Я имею дело с той же проблемой, что и ОП. Пока что это возможно с помощью tcmalloc. Я нашел два решения:

  1. скомпилируйте вашу программу с помощью tcmalloc, а затем запустите ее как:

    env TCMALLOC_RELEASE=100 ./my_pthread_soft
    

    в документации упоминается, что

    Разумные ставки находятся в диапазоне [0,10].

    но 10 не кажется мне достаточно (то есть я не вижу изменений).

  2. найдите где-нибудь в своем коде, где было бы интересно освободить всю освобожденную память, а затем добавьте этот код:

    #include "google/malloc_extension_c.h" // C include
    #include "google/malloc_extension.h"   // C++ include
    
    /* ... */
    
    MallocExtension_ReleaseFreeMemory();
    

Второе решение было очень эффективным в моем случае; первое было бы здорово, но не очень удачно, например, сложно найти правильный номер.

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

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

Краткий ответ: чтобы заставить подсистему malloc возвращать память для ОС, используйте malloc_trim(). В противном случае поведение возврата памяти зависит от реализации.

FreeBSD 12-х malloc(3)использует jemalloc 5.1, который возвращает освобожденную память ("грязные страницы") в ОС, используяmadvise(...MADV_FREE).

Освободившаяся память возвращается только после задержки, контролируемой opt.dirty_decay_ms а также opt.muzzy_decay_ms; см. страницу руководства и этот вопрос о реализации очистки неиспользуемых грязных страниц на основе распада.

Более ранние версии FreeBSD поставлялись со старыми версиями jemalloc, который также возвращает освобожденную память, но использует другой алгоритм, чтобы решить, что и когда очищать.

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