Про выделение динамической памяти, или почему firefox так много
памяти потребляет.
(Последний раз обновлено 24 августа 2006 года).
Всё началось с того, что все заметили, как много оперативной памяти
кушает firefox. А также то, что он не возвращает её системе после
закрытия всех открытых web-страниц.
Например, здесь с помощью функции malloc_stats() показано, что это не
утечки памяти: https://bugzilla.mozilla.org/show_bug.cgi?id=324081#c17.
А здесь это показано с помощью некой "leak-gauge tool"
https://bugzilla.mozilla.org/show_bug.cgi?id=324081#c9.
Оказывается, что реализация malloc в glibc выделяет память в куче с
помощью
brk/sbrk, т.е. одним большим связным куском. Эта куча может уменьшаться
только с конца, освободить память в середине невозможно (не считая
вызова madvise). Так что даже один байт в конце кучи не позволяет
отдать память системе. И malloc размещает переменные
в памяти по сути просто друг за другом. Это создаёт условия для такого
явления, как фрагментация памяти.
Существенно улучшить положение может аллокатор, основанный на mmap
(mmap--вызов, выделяющий память отдельной областью, пропорционально
размеру страницы, 4096 байт) и на соответствующих алгоритмах размещения
переменных в памяти.
Можно использовать довольно простой аллокатор из
OpenBSD-libc. Вообще-то, он был создан для другой цели, обеспечения
безопасности -- защиты от ошибки переполнения буфера...
но пришлось использовать его по причине отсутствия "навороченного"
mmap-based аллокатора. Предварительная версия для ОС Linux может быть найдена здесь в виде готового файла (и здесь
в виде патча к версии 1.83 из cvs). Собирать так: gcc -shared -fPIC
-O2 OpenBSD_malloc_Linux.c -o malloc.so, запускать так:
LD_PRELOAD=/path/to/malloc.so firefox.
Этот аллокатор можно использовать и с другими программами, но не
со всеми работает (например, с emacs не работал, а также, говорят, с
некоторыми сборками KDE). Но firefox почти у всех (и у меня всегда)
работает.
В общем, ищется программист, который бы написал более навороченный (чем в OpenBSD) аллокатор.
Статистика аллокаций.
Количество аллокаций определённого числа байт показана здесь.
А вот более старая информация (с картинками!).
Объяснение явления.
1. Почему память не возвращается?
Ответ на этот вопрос очевиден--не все переменные удаляются, и некоторые
из них "затыкают" кучу с конца.
2. Если закрыть все
web-страницы, кроме одной, а затем продолжить использовать браузер, то почему потребление памяти снова растёт и
растёт неограниченно--ведь в куче должно было остаться
достаточно места от предыдущих web-страниц?
Из эксперимента ясно, что общий объём всех аллокаций в ~500M при 50M
занятой памяти. Размер аллокаций разный--есть и в 20 байт, есть и в
4000 байт, причём малых аллокаций во много раз больше. Таким образом,
если в куче появляется большая дырка, то она тут же занимается меньшими
по сравнению с ней кусками, в промежутках между которыми набросано
много совсем мелких кусков размером ~20 байт, некоторые из этих мелких
кусков не удалятся--а это значит, что после того, как меньшие куски
будут удалены, большая дырка разобъётся на несколько меньших дырок.
Вспомнив, что такой процесс происходит порядка 500M/50M=10 раз,
становится понятно, что используемая за время даже небольшого сеанса
превращается в кашу, т.е. в куче аллокированные куски разделены
небольшими промежутками, больших дырок в куче нет. А это значит, что
когда firefox'у понадобится аллокировать большой кусок, то в куче для
него не окажется достаточного промежутка, и прийдётся делать это
расширяя всю кучу с помощью всё той же brk/sbrk.
Обе эти проблемы могут большей частью решены хорошим аллокатором,
основанным на mmap.