Про выделение динамической памяти, или почему 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.