попробавал кстати,
один и тот же набор страниц в случае с openbsd
40M, но при закртии постепенно отдается и сокращается до 36M
когда остается одна страница с LORом
в случае с аллокатором по умолчанию как прыгает до 36М при открытии всех страниц,
так и остается.
Die-Hard, я не понимаю. Проблема есть для конкретного приложения. Вообще-то, в glibc malloc проблемы нет -- он работает как и должен, т.е. максимально быстро, что хорошо для маленьких unix-way программок типа ps, ls и т.д.
Хочешь, сформулируй так, что языки C/C++ плохо подходят для "long-lived applications", особенно при неграмотном написании :). Только легче от этого не станет -- mozilla/firefox не переписать заново.
Есть несколько толстых приложений. Они отжирают память.
Если просуммировать все VM этих приложений, то зашкалит, заведомо за всю РАМ вместе со свопом.
Но это ничего не значит. Реально ж распределены только отожранные страницы!!
Идея в том, что каждый раз вы заказываете памяти по максимуму, скажем, тысячу страниц. Реально используете только одну страницу, и делаете free().
Память системе не возвращается, но в следующий раз вы опять заказываете
тысячу (+1) страниц. И опять ваша ВМ распухает. Но опять вы реально отъедаете только одну страницу. И, скорее всего, это будет та же самая страница, что была в первый раз!
При неудачной последовательности free() -- malloc(), вы постоянно заказываете новые страницы. Ваша ВМ пухнет, но -- ТОЛЬКО "на бумаге!"
Вы видите, как в ТОПе ваша аппликуха становится все толще. Но реально вы по-прежнему работаете только с одной страницей!
Мой вопрос, может, кто (idle?) знает, не тот ли это случай...
То, что ты описываешь -- это когда произведена аллокация, но обращения по соответствующему адресу на соответсвующей странице памяти не было, и если вообще не было обращения к этой странице, конечно.
Во-первых, firefox СОВСЕМ не такой случай.
Во-вторых, то, что показывает top в графе RES -- это вполне реальное потребление оперативной памяти процессом (о VM в графе VIRT речи не идёт, оно действительно в линуксе как-то странно считается). Для того, чтобы это проверить, можешь написать простейший тестик, раз уж нам не веришь.
За полтора месяца нам с zov'ом всё-таки удалось привлечь внимание к проблеме. Ещё через пару месяцев думать начнут. А потом и до того дойдёт, что делать что-то будут :-))
А если серьёзно, то всё уже рассказали. Печально, но похоже во всём мире не находится программистов, которые захотели этим заниматься. Вот и рассказывайте ламерам сказки о светлом будущем линукса.
> Мой вопрос, может, кто (idle?) знает, не тот ли это случай...
Можно же проверить. Например, написать malloc почти на всю память и реально отожрать её, заполнив чем-нибудь. Посмотреть, что происходит в процессе отжирания.
Ты, это, давай не ругайся, плиз. Это нормально, что когда что-то делаешь, всем на это плевать. Так абсолютно во всём. :) Зато когда/если ты победишь, тем больше будет тебе почёт. :)
> Можно же проверить. Например, написать malloc почти на всю память и реально отожрать её, заполнив чем-нибудь. Посмотреть, что происходит в процессе отжирания.
Да конечно можно!
Просто, сдается мне, все это -- известные вещи...
Я лично до сих пор Огнелис юзаю (хоть и ДОСТАЛ он меня, времени нету на что другое соскочить), много проблем, да, но вот опИсанной пока не наблюдал...
стало интересно, попробовал валгриндом (просто информацию о браузере):
valgrind --leak-check=full mozilla -v
вывод (часть):
Mozilla 1.7.11, Copyright (c) 2003-2004 mozilla.org <developer build>
==18355==
==18355== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 8 from 5)
==18355== malloc/free: in use at exit: 295,799 bytes in 952 blocks.
==18355== malloc/free: 6,371 allocs, 5,419 frees, 533,768 bytes allocated.
==18355== For counts of detected errors, rerun with: -v
==18355== searching for pointers to 952 not-freed blocks.
==18355== checked 279,040 bytes.
==18355==
==18355== 10 bytes in 1 blocks are definitely lost in loss record 1 of 4
==18355== at 0x4A19B05: malloc (vg_replace_malloc.c:149)
==18355== by 0x45EAEA: xmalloc (in /bin/bash)
==18355== by 0x4706F9: sh_canonpath (in /bin/bash)
==18355== by 0x45FB91: change_to_directory (in /bin/bash)
==18355== by 0x45FE1B: cd_builtin (in /bin/bash)
==18355== by 0x4282A8: execute_builtin (in /bin/bash)
==18355== by 0x42B31E: execute_simple_command (in /bin/bash)
==18355== by 0x4292AD: execute_command_internal (in /bin/bash)
==18355== by 0x42CC1B: execute_connection (in /bin/bash)
==18355== by 0x4293B6: execute_command_internal (in /bin/bash)
==18355== by 0x42B8E0: execute_command (in /bin/bash)
==18355== by 0x42922E: execute_command_internal (in /bin/bash)
==18355==
==18355== LEAK SUMMARY:
==18355== definitely lost: 10 bytes in 1 blocks.
==18355== possibly lost: 0 bytes in 0 blocks.
==18355== still reachable: 295,789 bytes in 951 blocks.
==18355== suppressed: 0 bytes in 0 blocks.
==18355== Reachable blocks (those to which a pointer was found) are not shown.
==18355== To see them, rerun with: --show-reachable=yes
зы эту багу возможно уже пофиксели (у меня 1.7.11) почемуб этим тестом не прогнать и не выяснить?
> зы эту багу возможно уже пофиксели (у меня 1.7.11) почемуб этим тестом не прогнать и не выяснить?
А как?
m1@michael:~$ valgrind --leak-check=full firefox -v
==3894== Memcheck, a memory error detector.
==3894== Copyright (C) 2002-2005, and GNU GPL'd, by Julian Seward et al.
==3894== Using LibVEX rev 1575, a library for dynamic binary translation.
==3894== Copyright (C) 2004-2005, and GNU GPL'd, by OpenWorks LLP.
==3894== Using valgrind-3.1.1-Debian, a dynamic binary instrumentation framework.
==3894== Copyright (C) 2000-2005, and GNU GPL'd, by Julian Seward et al.
==3894== For more details, rerun with: -v
==3894==
--3894-- DWARF2 CFI reader: unhandled CFI instruction 0:50
--3894-- DWARF2 CFI reader: unhandled CFI instruction 0:50
==3894== Conditional jump or move depends on uninitialised value(s)
==3894== at 0x4009313: (within /lib/ld-2.3.6.so)
==3894== by 0x417FEA9: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x400BA5E: (within /lib/ld-2.3.6.so)
==3894== by 0x418055A: _dl_open (in /lib/tls/libc-2.3.6.so)
==3894== by 0x4181AEC: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x400BA5E: (within /lib/ld-2.3.6.so)
==3894== by 0x4181C4D: __libc_dlopen_mode (in /lib/tls/libc-2.3.6.so)
==3894== by 0x40984DA: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x408FFB4: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x40908A9: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x40F99F8: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x40EE305: mbrtowc (in /lib/tls/libc-2.3.6.so)
==3894==
==3894== Conditional jump or move depends on uninitialised value(s)
==3894== at 0x4008FB0: (within /lib/ld-2.3.6.so)
==3894== by 0x417FEA9: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x400BA5E: (within /lib/ld-2.3.6.so)
==3894== by 0x418055A: _dl_open (in /lib/tls/libc-2.3.6.so)
==3894== by 0x4181AEC: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x400BA5E: (within /lib/ld-2.3.6.so)
==3894== by 0x4181C4D: __libc_dlopen_mode (in /lib/tls/libc-2.3.6.so)
==3894== by 0x40984DA: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x408FFB4: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x40908A9: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x40F99F8: (within /lib/tls/libc-2.3.6.so)
==3894== by 0x40EE305: mbrtowc (in /lib/tls/libc-2.3.6.so)
Mozilla Firefox 1.5.0.4, Copyright (c) 1998 - 2006 mozilla.org
И что с этим делать, если оно вот так сваливается?
если взглянуть на код firefox, то у него например есть
что-то типа garbage collerctor, и он написан так чтобы заменять
стандартную malloc. Возможно он используется только в отладочных целях,
а возможно и нет.
All versions of Firefox no doubt leak memory - it is a common problem with software this complicated.
Честно говоря, когда я читаю такое, мне дальше уже не интересно.
Это даже не деТТство, это быдлокодерство как менталитет...
Кстати, если вы пИшете свой сборщик мусора, то, очевидно, все теоретические изыскания идут лесом.
Я не понимаю -- менеджер памяти написать, действительно, дело наскольких часов. Прощупать его и заточить под конкретные нужды -- дело нескольких дней. Вся литература доступна, все алгоритмы обсосаны по самую шапочку...
2mr:
Ок, я проникся проблемой -- как максимально искривить теоретически лучший мемори манагер, чтобы нейтрализовать кривизну рук разработчиков Огелиса. Так?
Предлагается систематически отдавать освобожденные страницы операционке, как я понял. Но не сразу, а организовать нечто типа garbage collector'а. Так?
В качестве готовой реализации предлагается портануть решение из openbsd -- по некоторым (приведенным mr выше) соображениям оно, вроде, подходит. И все, что требуется от коммьюнити -- приложить руку к стабилизации порта.
>плохо найти тестик, в котором воспроизводилась эта ошибка
тест который воспроизведет ту же ошибку не даст ничего,
пример:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_MALLOC 100000
#define NALLOC 10000
static void fill_mem(char *p, int len)
{
memset(p, 'Z', len);
}
int main()
{
int i;
char *p[NALLOC];
for (i = 0; i < NALLOC; ++i) {
int len = rand() % MAX_MALLOC;
switch (i % 3) {
case 0:
p[i] = malloc(len);
break;
case 1:
p[i] = realloc(NULL, len);
break;
case 2:
p[i] = realloc(p[i - 1], len);
break;
}
fill_mem(p[i], len);
}
for (i = NALLOC - 1; i >= 0; --i)
free(p[i]);
return 0;
}
создает туже ошибку, но дело в том что просто программа не верна,
с glibc она выдаст что-нибудь типа double free corruption.
Во-первых, не искривить, а запользовать совершенно другой аллокатор, отнюдь не кривой.
Во-вторых, о gc речи не идёт.
Glibc malloc основан на brk и не может отдавать память вообще, если конец кучи затыкается хотя бы одним байтом. И это очень плохо, от такого поведения могут страдать многие программы, далеко не только firefox, хотя в небольших программах это можно исправить небольшой модификацией -- установлением правильного порядка аллокаций, или использованием простейшего mmap'нутого аллокатора, ну а с перцами типа firefox всё сложнее гораздо, тут нужны более сложные алгоритмы размещения переменных по mmap'нутым страницам. Чисто экспериментально проверено, что аллокатор из openbsd этому удовлетворяет. А какие именно там алгоритмы -- в этом ещё я не разбирался.
Про комьюнити: просто хотелось найти "профессионального программиста", который бы сделал работу по отладке этого аллокатора :) Я не программист, боюсь, эта работа у меня займёт много времени :(
>создает туже ошибку, но дело в том что просто программа не верна,
смысл в том, что ошибка которую удастся повторить в какой-нибудь другой может быть следствием ошибки самой программы, а не openbsd-malloc,
и данный случай может быть ошибкой firefox,
так что надо понять что же происходит в firefox, а не искать пример где воспроизводится эта ошибка.
Кстати говоря, очень может быть, что это ошибка firefox.
Мне zov говорил, что у него firefox 1.0.6 в линуксе с новым аллокатором почти безошибочно работает. Любопытно: под openbsd тестировался только firefox 1.0.x, так как 1.5 там ещё не было.
Кроме того, zov говорил, что у него вся KDE с кучей пргограмм (!) без всяких ворнингов работает с этим аллокатором. Вот и думай, что на самом деле не так работает...
А понять, что происходит в firefox, трудно, потому что программа эта очень большая :-/
Там три разных аллокатора, в том числе mmap'нутый, правда на вид довольно простой. Я как-то не смог сходу заставить его заработать, можно ещё попытаться, конечно.
Кстати, я где-то читал, что в стародавние времена (типа первой половины/середины 90-х) в линуксе то ли был, то ли предлагался в качестве главного, основанный на mmap аллокатор. Так-то :)
Насколько мне ни изменяет память, в glibc есть некий порог, после которого используется mmap. И порог этот постоянно меняется (от версии к версии), и вообще на него можно влиять. Есть такая функция, mallopt
называется...
Да, про переменную MMAP_THRESHOLD, на самом деле, нам хорошо известно. На этом использование mmap в glibc malloc заканчивается.
Насколько я слышал, оно используется в иксах. Программе firefox это точно не поможет, потому что там аллокации в среднем по 20 байт, т.е. много меньше размера страницы памяти, 4096 байт.
Программе firefox это точно не поможет, потому что там аллокации в среднем по 20 байт, т.е. много меньше размера страницы памяти, 4096 байт.
Что-то я не пойму...
Судя по описанию, выставление порога в размер в несколько байт (кстати, страница вовсе не обязательно 4096 байт) полностью эмулирует БСДшный механизм. Или я не прав?
>Судя по описанию, выставление порога в размер в несколько байт (кстати, страница вовсе не обязательно 4096 байт) полностью эмулирует БСДшный механизм. Или я не прав?
Неа... При выставлении "порога в несколько байт" на каждую переменную в несколько байт будет выделяться (отдельно!) целая страница в 4096 байт. Память оно, конечно, будет возвращать, но только само потребление памяти увеличится во много сотен раз :))
А можно еще раз коротко сформулировать, зачем вообще возвращать память системе?
Иначе говоря:
Как постраничнвая отдача памяти системе может может помочь против фрагментации?
Еще раз, третьим способом:
Если память фрагментируется так, что целая страница может быть возвращена системе, то зачем ее вообще возвращать? Почему ее просто в пуле не оставить, как glibc, собственно, и делает?
>А можно еще раз коротко сформулировать, зачем вообще возвращать память системе?
>Если память фрагментируется так, что целая страница может быть возвращена системе, то зачем ее вообще возвращать?
Ты так спрашиваешь, что я имею право ответить -- просто, чтобы ею могли воспользоваться другие программы. Если какой-нибудь firefox всю память отжирает -- это нормально?
>Как постраничнвая отдача памяти системе может может помочь против фрагментации?
Определи фрагментацию как отношение общей занятой памяти к реально используемой. Страничку mmap'нутую можно отдать, а из огромного многостраничного куска, созданного brk, невозможно отдать ничего (не считая madvise()), кроме как с его конца, хотя в нём могут быть большие куски (временно) неиспользуемой памяти -- вот откуда для него фрагметация берётся.
Так это ж dlmalloc, на его основе написан ptmalloc, а на нём уже написан glibc malloc. Как уже говорилось, это лучший по фрагментации из аллокаторов СРЕДИ brk-based аллокаторов. (Да, его можно собрать без sbrk, только с mmap, но конкретной программе -- firefox'у -- это не помогает, потому что тут нужны специфические для mmap-based аллокатора алгоритмы).
>>Если память фрагментируется так, что целая страница может быть возвращена системе, то зачем ее вообще возвращать?
>Ты так спрашиваешь, что я имею право ответить -- просто, чтобы ею могли воспользоваться другие программы.
Не только. Имхо, могут появляться еще лишние обращения к swap:
пусть куча после аллокаций с sbrk(), записи во все аллокированные страницы и вызовов free() имеет вид ЗСССССССССССССЗ (где З -- занятая страница, C -- свободная), причем к свободным страницам достаточно долго не было обращений, чтобы ядро выбросило их в swap, при этом страницы 'C' остались за нашим процессом, так как перед этим в них уже записывали.
Пусть теперь процесс делает malloc(size) с size > 4096. malloc вернет указатель из середины кучи, при этом страница из swap опять перебрасывается в ОЗУ. Если страница не попала случайно в кэшированную часть swap, то этот вызов malloc займет больше времени, чем
mmap(0, (size/4096 + 1)*4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS ....);memset(...) при наличии свободной оперативной памяти.
При свободной оперативной памяти искуственно организуется лишний swapping. Аллокатор OpenBSD в этом случае сначала сделает munmap свободных страниц, а при вызове malloc сделает mmap(), к swap при этом обращений нет, openbsd-malloc отработает быстрее, чем glibc-malloc.
DISCLAIMER: я не специалист; пишу в основном на фортране; как linux работает со страницами, не знаю; ситуация вверху взята из головы. :))
Действительно, никакие трюки с гистерезисным параметром не помогают. Все, что умеет этот malloc() -- тупо выделять большие куски через mmap, а потом их возвращать, тупо отмаппливая. Они даже в чанки не вяжуться...
Все же, рассуждения zov'а убедили не до конца. Даже при поистине варварском обращении с памятью такого не бывает -- пример Оперы тому подтверждение. Если ликов нет, фрагментации особой тоже нет. Т.е. вопрос именно в том, как минимизировать последствия утечек.
Ядру от mmap'а, конечно, поплохеть не может -- достаточно взглянуть на любой strace, чтобы убедиться, что mmap/munmap -- самые частые сисколлы. Только вот дорогие они...
> ...тут нужны специфические для mmap-based аллокатора алгоритмы
Из более раннего поста:
> В том то и дело, что для программ типа firefox нужны более сложные алгоритмы размещения переменных в памяти, чем просто линейное -- иначе точно не будет преимущества перед brk-based mallocs. Оказалось, что в openbsd malloc эти алгоритмы хорошие.
А можно подробнее (или ссылку)? И что такое "...более сложные алгоритмы размещения переменных в памяти, чем просто линейное..."?
Можно попробовать накатать простейший аллокатор на mmap'e и сравнить тем, что у тебя. Возможно, не будет шибко медленнее, кстати. А возможно, проблема не в аллокаторе, а в багах Огнелиса -- просто glibcшный (де-)аллокатор их не замечает.
По утечкой нужно понимать не только то, что память забывают освобождать, теряя указатели. Если мы не уничтожаем распределенный объект "на всякий случай", то эффект будет такой же. У плюсатых пЫонэров, например, принято не уничтожать глобальные объекты -- а зачем, все равно в конце программы они сами уничтожатся! Никакой отладчик этого не отследит -- они ж ДЕЙСТВИТЕЛЬНО уничтожатся! Но -- в конце... А это приводит к фрагментации.
Вот интересное наблюдение я сегодня сделал. Я скачал и запустил live-cd дистрибутив mandriva one 2006 для проверки аллокатора. Там есть kde с соотв. прогами, firefox 1.0.8/gtk2, gtk 1.8.3.
Так вот, запускал я firefox (таким же образом, как и раньше), ворнингов получить не удалось! Мало того, я всю kde (со всеми производными программам, в т.ч. с konqueror и firefox) запускал с новым аллокатором, тоже никаких ворнингов.
Проверял на отдачу памяти firefox и konqueror, память отдают (напр., получилось в сеансе 25M->53M->37M для firefox, 21M->53M->39M для konqueror). Без соотв. LD_PRELOAD память они не отдавали вообще (для konqueror, напр., потребление менялось так: 20M->49M->49M).
Итак, этот аллокатор для mandriva one работает, и работает, похоже, без ошибок. А ведь zov проводил тестирование на нескольких компьютерах и говорил, что на некоторых работает без всяких ворнингов (!?). Причём для firefox'а это вроде бы не зависело от самой версии firefox'а.
Непосредственно на установленной системя я запустил с новым аллокатором kde с некоторыми программами и обнаружил, что ругаются, помимо firefox'а, только gkrellm, rccexternal, gedit, gam_server. Это было получено просто заменой wrtwarning("free_pages: pointer to wrong page") или того самого say очевидной строкой типа:
FPRINTERR("(%s)free_pages: pointer to wrong page",__progname);
И что же общего между этими программами? Да, они все связаны с gtk, а точнее с glib !..