Пропустить навигацию.
Главная

Потребление памяти в Linux

Всё началось с того, что все заметили, как много оперативной памяти кушает firefox. А также то, что он не возвращает её системе после закрытия всех открытых web-страниц. В этой небольшой заметке можно узнать почему это происходит и как с этим бороться.

Оказывается, что реализация malloc в glibc выделяет память в куче с помощью brk/sbrk, т.е. одним большим связным куском. Эта куча может уменьшаться только с конца, освободить память в середине невозможно (не считая вызова madvise). Так что даже один байт в конце кучи не позволяет отдать память системе. И malloc размещает переменные в памяти по сути просто друг за другом. Это создаёт условия для такого явления, как фрагментация памяти.

Существенно улучшить положение может аллокатор, основанный на mmap (mmap--вызов, выделяющий память отдельной областью, пропорционально размеру страницы, 4096 байт) и на соответствующих алгоритмах размещения переменных в памяти.

Можно использовать довольно простой аллокатор из OpenBSD-libc. Вообще-то, он был создан для другой цели, обеспечения безопасности -- защиты от ошибки переполнения буфера... но пришлось использовать его по причине отсутствия "навороченного" mmap-based аллокатора.

Предварительная версия для ОС Linux может быть найдена здесь. Собирать так:

gcc -shared -fPIC -O2 OpenBSD_malloc_Linux.c -o malloc.so

запускать так:

LD_PRELOAD=/path/to/malloc.so firefox.

Этот аллокатор можно использовать и с другими программами, но не со всеми работает (например, с emacs не работал, а также, говорят, с некоторыми сборками KDE). Но firefox почти у всех (и у меня всегда) работает.

Объяснение явления.
1. Почему память не возвращается?
Ответ на этот вопрос очевиден--не все переменные удаляются, и некоторые из них "затыкают" кучу с конца.

2. Если закрыть все web-страницы, кроме одной, а затем продолжить использовать браузер, то почему потребление памяти снова растёт и растёт неограниченно--ведь в куче должно было остаться достаточно места от предыдущих web-страниц?
Из эксперимента ясно, что общий объём всех аллокаций в ~500M при 50M занятой памяти. Размер аллокаций разный--есть и в 20 байт, есть и в 4000 байт, причём малых аллокаций во много раз больше. Таким образом, если в куче появляется большая дырка, то она тут же занимается меньшими по сравнению с ней кусками, в промежутках между которыми набросано много совсем мелких кусков размером ~20 байт, некоторые из этих мелких кусков не удалятся--а это значит, что после того, как меньшие куски будут удалены, большая дырка разобъётся на несколько меньших дырок. Вспомнив, что такой процесс происходит порядка 500M/50M=10 раз, становится понятно, что используемая за время даже небольшого сеанса превращается в кашу, т.е. в куче аллокированные куски разделены небольшими промежутками, больших дырок в куче нет. А это значит, что когда firefox'у понадобится аллокировать большой кусок, то в куче для него не окажется достаточного промежутка, и прийдётся делать это расширяя всю кучу с помощью всё той же brk/sbrk.

Обе эти проблемы могут большей частью решены хорошим аллокатором, основанным на mmap.

В заключение стоит сказать, что потребление памяти оперой на данный момент у меня снизилось в 3(!) раза, FF тоже кушает заметно меньше (не привожу цифры, ибо его практически не использую). На других приложениях пока не тестировал.

Основано на информации с сайта mr.himki.net