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

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

Что есть, в этом отношении поведение:

  • ptmalloc 1, 2 (по умолчанию glibc) или 3
  • dlmalloc
  • tcmalloc (google threaded malloc)
  • solaris 10-11 default malloc и mtmalloc
  • FreeBSD 8 default malloc (jemalloc)
  • Содержит malloc?

Update

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

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

Ответ 1

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

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

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

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

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

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

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

  • используя madvise() системный вызов внутри свободной реализации glibc.

Ответ 2

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

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

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

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

Ответ 3

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

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

    env TCMALLOC_RELEASE=100 ./my_pthread_soft
    

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

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

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

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

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

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

Ответ 4

У меня была аналогичная проблема в моем приложении, после некоторого исследования я заметил, что по какой-то причине 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

Ответ 5

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

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