LINUX.ORG.RU
ФорумTalks

Фрагментация памяти

 ,


0

1

Кто-нибудь проводил исследования, какие современные языки (компиляторы, рантаймы и пр.) в большей мере страдают от этого? Например, запустили программу, которая обрабатывает массив данных на диске мелкими порциями (0,1-10М для определённости), она приступила, через сутки отъела 2Г памяти и упала. Не потому что кривая, а потому что такой у рантайма такой менеджер памяти. Или не через сутки, а через неделю. Но упадёт гарантировано.

★★★★★

Управление кэшем в линуксе достаёт своими фрагментациями, особенно при выходе из спячки - ОЗУ хватает, а оно всё равно пихает из него в кэш и создаёт тормоза и глюки. На чём это написано, посмотри сам.

Napilnik ★★★★★ ()

джаваскрипт наверняка страдает, сам не замерял но по ощущениям так вот чувствуется что вот прям фрагментируется.

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

uin ★★ ()

В большей - сложно сказать. А в меньшей - JVM и .NET-based языки должны быть вообще свободны от проблем с фрагментацией.

qrck ★★ ()

C и плюсы при неосторожном обращении фрагментируют невозбранно.

Valeg ★★★ ()

программу, которая обрабатывает массив данных на диске мелкими порциями (0,1-10М для определённости), она приступила, через сутки отъела 2Г памяти и упала. Не потому что кривая,

именно потому, что кривая. Результат, возвращаемый функциями выделения и (ты будешь смеяться) освобождения памяти, надо обрабатывать, а не падать. Это как минимум. А вообще, в рекомендациях к написанию программ, претендующих на надежность, динамической памятью пользоваться прямо запрещается. Потому что надо понимать природу всего этого - эвристики, неожиданное и внезапное поведение.

lenin386 ★★★ ()
Последнее исправление: lenin386 (всего исправлений: 3)

Так мелкие порции, это же хорошо. И чем меньше, тем лучше.
Нулевой указатель от malloc получают не когда памяти нет, а когда негде взять кусок запрошенного размера.

А если сборщик мусора в языкнейм старые куски не освобождает (утечка), то какая разница, какого они размера?

aidaho ★★★★★ ()
Ответ на: комментарий от Valeg

Фрагментация - естественное свойство динамической памяти вне зависимости от языка. Чудес тут быть не может. Не хочешь неожиданностей - не пользуйся этим. Если иначе нельзя - максимально продумывай последствия (обрабатывай возвращаемые значения блжд)

lenin386 ★★★ ()
Последнее исправление: lenin386 (всего исправлений: 1)
Ответ на: комментарий от qrck

должны быть вообще свободны от проблем с фрагментацией

пожалей носки мои.

lenin386 ★★★ ()
Ответ на: комментарий от Valeg

это если программу писал говнокодер. там есть абсолютно все средства для управления памятью и никаких проблем с этим нет вообще. если какой-то неосилятор нишмог - это не проблемы языков или, тем более, компиляторов.

Iron_Bug ★★★★ ()

Фрагментируется-то виртуальная, отъели мы 2 гига или 256, какая разница?

Gary ★★★★★ ()
a = malloc(n);
for (;;n = n<<1 + 1) {
  b = malloc(n);
  free(a);
  a = b;
}

И ничего не поделаешь.

baka-kun ★★★★★ ()
Ответ на: комментарий от lenin386

Ну так там сборщик мусора автоматически дефрагментирует память. Все что не припинено явно - может и будет перемещено сборщиком мусора, так что бы в каждой из куч не было дырок

qrck ★★ ()
Ответ на: комментарий от qrck

Ну так там сборщик мусора автоматически дефрагментирует память.

Ну, так _когда_ он это делает ? Твоей программе, например, пришло сообщение, что у пациента остановилось сердце. А тут на сцену выходит сборщик мусора.

lenin386 ★★★ ()
Последнее исправление: lenin386 (всего исправлений: 1)
Ответ на: комментарий от lenin386

Фрагментация - естественное свойство динамической памяти вне зависимости от языка. Чудес тут быть не может.

Перемещающий сборщик мусора решает проблему фрагментации.

i-rinat ★★★★★ ()
Ответ на: комментарий от lenin386

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

Это требования для нишевого софта. Я сомневаюсь, что у меня на компе есть хотя бы одна программа, которая сможет пройти сертификацию по MISRA C или MISRA C++.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Это требования для нишевого софта

Да любой софт - нишевый.

Я сомневаюсь, что у меня на компе есть хотя бы одна программа, которая сможет пройти сертификацию по MISRA C или MISRA C++.

Сертификация - это тупо покупка бумаги. _Тупо_ _покупка_ _бумаги_.

lenin386 ★★★ ()
Ответ на: комментарий от lenin386

Сертификация - это тупо покупка бумаги. _Тупо_ _покупка_ _бумаги_.

И что ты этим доказал? _И_ _что_ _ты_ _этим_ _доказал_?

i-rinat ★★★★★ ()
Ответ на: комментарий от lenin386

Ну, так _когда_ он это делает ? Твоей программе, например, пришло сообщение, что у пациента остановилось сердце. А тут на сцену выходит сборщик мусора.

Воспрос был про фрагментацию, а не про стоимость fragmentation-free памяти ;)

Никто не говорил, что писать для managed-окружения - легко. Для задачь вроде этой - существуют механизмы минимизации рисков того, что сборка мусора случится тогда, когда это совсем не кстати. Ну и там где требуется жесткое реальное время - там вообще все надо писать по другому, что на managed что на native языках, и хорошим тоном будет вообще отказаться от любых алокаций во время жизни приложения, т.е. выделять все заранее при инициализации, и потом пользоваться «заготовленным». А с подобной практикой - фрагментация заранее не страшна, т.к. фрагментироваться нечему.

qrck ★★ ()
Ответ на: комментарий от aidaho

Так мелкие порции, это же хорошо. И чем меньше, тем лучше.

Создалась переменная и сразу разбросалась по 1000 фрагментам - что тут хорошего?

Napilnik ★★★★★ ()
Ответ на: комментарий от qrck

JVM и .NET-based языки должны быть вообще свободны от проблем с фрагментацией.

Кто-нибудь их тестировал? Как?

question4 ★★★★★ ()
Ответ на: комментарий от lenin386

Результат, возвращаемый функциями выделения и (ты будешь смеяться) освобождения памяти, надо обрабатывать, а не падать.

Сообщения вида «Out of memory exception. Restarting server.» я отнёс к «падениям».

question4 ★★★★★ ()
Ответ на: комментарий от aidaho

А если сборщик мусора в языкнейм старые куски не освобождает (утечка), то какая разница, какого они размера?

Об этом и вопрос. Какие языки и библиотеки больше грешат утечками и несвоевременным перемещением, и как это проверяют?

question4 ★★★★★ ()
Ответ на: комментарий от Gary

Фрагментируется-то виртуальная, отъели мы 2 гига или 256, какая разница?

В 32-битных системах сколько памяти на процесс? 2 или 4? Пусть будет не 2, а 4. Или у линуксов и это ограничение расширено?

Подразумевал, что всю память съедает один процесс. Конечно, можно его рестартовать, но так неинтересно.

question4 ★★★★★ ()
Ответ на: комментарий от uin

джаваскрипт наверняка страдает, сам не замерял но по ощущениям так вот чувствуется что вот прям фрагментируется.

1000+ раз перезапустил страницу с обновленным кодом, так вот через какое то время браузер начинает тупить и грохается

фуфлофокс.

Вот из-за него я помимо языков упомянул рантаймы. Уже разбирали, что проблема проявляется только для Фаерфокса в сочетании с glibc. По отдельности, ни код FF, ни GCC, ни glibc в столь страшной фрагментации не замечены.

Но это одиночный пример, а я спрашиваю, были ли системные исследования?

question4 ★★★★★ ()
Ответ на: комментарий от Iron_Bug

это если программу писал говнокодер

Если программу писал большой коллектив, что-то такое обязательно найдётся.

question4 ★★★★★ ()
Ответ на: комментарий от i-rinat

Перемещающий сборщик мусора решает проблему фрагментации.

Где он есть? Как он называется по-английски?

question4 ★★★★★ ()
Ответ на: комментарий от question4

Готового списка у меня нет, но можно начать вот отсюда: https://en.wikipedia.org/wiki/Tracing_garbage_collection#Moving_vs._non-moving

А дальше просто перебираешь алгоритмы, смотришь, перемещают ли они данные. Обычно для каждого алгоритма есть несколько примеров использования. Так для mark-compact написано, что подобные используются в Common Language Runtime (.NET) и Glasgow Haskell Compiler.

i-rinat ★★★★★ ()
Ответ на: комментарий от question4

32-битных системах сколько памяти на процесс? 2 или 4?

прозрачно, без костылей - 2.

lenin386 ★★★ ()
Ответ на: комментарий от Napilnik

Создалась переменная и сразу разбросалась по 1000 фрагментам - что тут хорошего?

Сама создалась и сама разбросалась? Ох уж эти пасквилярты-теоретики. Вы хуже диванных специалистов.

andreyu ★★★★★ ()
Ответ на: комментарий от lenin386

Результат, возвращаемый функциями выделения и (ты будешь смеяться) освобождения памяти, надо обрабатывать,

void free(void *ptr);

И какой результат ты тут собрался обрабатывать?

Zenom ★★★ ()
Ответ на: комментарий от lenin386

> Ну так там сборщик мусора автоматически дефрагментирует память

Ну, так _когда_ он это делает ?

Очень часто он это делает. Перемещение живых объектов из Eden Space происходит при каждой минорной сборке мусора, которая не требует длительных пауз и происходит регулярно.

Zenom ★★★ ()
Ответ на: комментарий от aidaho

2.

# include <stdio.h>
# include <stdlib.h>

void main (void)
{
	void * rv;

	for (int i=0; i<1000; i++)
	{
		rv = malloc (10*1024*1024);

		if (!rv)
			break;

		printf ("%dM \n", i*10);
	}
}

...... 1960M 1970M

lenin386 ★★★ ()
Последнее исправление: lenin386 (всего исправлений: 1)
Ответ на: комментарий от lenin386

3054 ровно.

Где ты нашёл линукс с 2G/2G сплитом?

aidaho ★★★★★ ()
Ответ на: комментарий от i-rinat

These are called «non-moving» and «moving» (or, alternatively, «non-compacting» and «compacting») garbage collectors

Спасибо за ссылку на раздел.

question4 ★★★★★ ()
Ответ на: комментарий от Napilnik

Создалась переменная и сразу разбросалась по 1000 фрагментам

Вау. Просто wow.

i-rinat ★★★★★ ()
Ответ на: комментарий от question4

32-битные системы сейчас наверное редкость

Подразумевал, что всю память съедает один процесс

Не совсем понятен расклад, как это происходит

Если процесс высвобождает память кусочков, которые считал и они ему больше не нужны, то как он сожрёт всю память? Виртуальную память мы, в некотором роде, можем фрагментировать сколько хотим. Если мы займём одну страницу из двух гигабайт, которые запросили, и в системе разрешен overcommit, ничего плохого не случится и мы будем использовать только одну страницу.

Если процесс этого не делает, то хочешь-не хочешь, а если для работы надо больше памяти чем есть в системе, то тут уже никакой «менеджер памяти» не поможет

Gary ★★★★★ ()
Ответ на: комментарий от question4

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

Iron_Bug ★★★★ ()
Ответ на: комментарий от Gary

Если процесс высвобождает память кусочков, которые считал и они ему больше не нужны, то как он сожрёт всю память?

Аллокатор в Glibc устроен немного не так. Для маленьких аллокаций двигается граница памяти процесса. Её можно подвинуть обратно, но только до первого использованного куска. Нельзя просто отдать системе часть этой памяти из середины, можно только надеяться, что система сбросит эти участки в своп, когда понадобится память. Из-за особенностей работы кода управления памятью ядро Linux с неохотой отбирает память, которая активно использовалась процессом. Так что сначала вымоется дисковый кеш, и только потом память начнут забирать у процессов. И то с неохотой. Примерно в это время ты словишь неслабые такие тормоза от свопа.

Если процесс 32-битный, у него ещё и адресное пространство кончится.

Вот тебе пример для экспериментов:

#include <stdio.h>
#include <stdlib.h>

static void printmaps(void) {
  char buf[4000];
  FILE *fp = fopen("/proc/self/maps", "rb");

  printf("\033[2J\033[0;0H");
  while (!feof(fp)) {
   int n = fread(buf, 1, sizeof(buf), fp);
   fwrite(buf, 1, n, stdout);
  }
  fflush(stdout);

  fclose(fp);
}

int main(void) {
  while (1) {
    for (int k = 0; k < 0x7ffff; k++)
      malloc(50);

    printmaps();
  }
}

Обрати внимание на то, что растёт только память [heap].

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Отдавать память из середины назад в систему не обязательно, если мы будем «освобождать» её через free, то тогда аллокатор glibc должен спокойно повторно её использовать при следующей аллокации, и размер блока бесконтрольно расти не должен.

Ну и вернуть память из середины всё-таки наверное можно через madvise, насколько я знаю он вызывается при вызове malloc_trim. Я правда не знаю, насколько это верно про то что кеш вымоется раньше чем высвободятся такие страницы, но что-то то что они в своп будут сброшены, мне не верится совсем.

Вообще мне что-то интересно стало, вроде есть такой /proc/[pid]/pagemap, где можно посмотреть реальное распределение страниц памяти процесса, осталось его считать и в виде картинки показать :^)

Gary ★★★★★ ()
Ответ на: комментарий от Gary

если мы будем «освобождать» её через free, то тогда аллокатор glibc должен спокойно повторно её использовать при следующей аллокации, и размер блока бесконтрольно расти не должен.

Вообще-то фрагментация — она как раз об этом. Мелкие промежутки свободной памяти не вмещают блок нужного размера, поэтому аллокатор двигает границу снова.

Ну и вернуть память из середины всё-таки наверное можно через madvise, насколько я знаю он вызывается при вызове malloc_trim.

Там MADV_DONTNEED, владение памятью сохраняется. Ну и нужно, чтобы свободный блок покрывал хотя бы одну страницу.

но что-то то что они в своп будут сброшены, мне не верится совсем.

Ну вообще-то ядро обязано сохранить данные в этой памяти. Можно, конечно, дырку пробить, но в glibc этого не делают. Интересно, чего больше от пробивания дырок будет, проблем или пользы?

Вообще мне что-то интересно стало, вроде есть такой /proc/[pid]/pagemap, где можно посмотреть реальное распределение страниц памяти процесса, осталось его считать и в виде картинки показать :^)

Есть утилита pagemon.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Вот кстати посмотрел через pagemon, malloc_trim «пробивает дырку»

#include <iostream>
#include <vector>

#include <malloc.h>
#include <sys/types.h>
#include <unistd.h>

int main(void) {
	std::cout << "My pid: " << getpid() << std::flush;
	int sz = 0x2ffff;
	std::vector<char*> addr;
	addr.reserve(sz);
	
	for (int i = 0; i < sz; i++)
	{
		addr.push_back(new char[32]);
	}
	system("read");
	
	for (int i = 1; i < sz - 1; i++)
	{
		delete[](addr.at(i));
	}

	system("read");
	malloc_trim(0);
	system("read");
}
Gary ★★★★★ ()
Последнее исправление: Gary (всего исправлений: 1)
Ответ на: комментарий от i-rinat

Вот кстати смотрю в madvise про DONTNEED

After a successful MADV_DONTNEED operation, the semantics of memory access in the specified region are changed: subsequent accesses of pages in the range will succeed, but will result in either repopulating the memory contents from the up-to-date contents of the underlying mapped file (for shared file mappings, shared anonymous mappings, and shmem-based techniques such as System V shared memory segments) or zero- fill-on-demand pages for anonymous private mappings.

Для анонимных маппингов данные сохраняться не должны. Правда это получается всё про malloc_trim, который при нормальной работе наверное никогда не вызывается.

Так что да, вопрос в том насколько malloc эффективно решает вопрос фрагментации в выделенном ему участке(ах). Вообще для кусков по 10 МБ, как указал ТС, наверное вообще будет использоваться mmap, и может начнётся фрагментация уже реальной памяти и тогда malloc вряд ли что-то сможет сделать.

Gary ★★★★★ ()
Ответ на: комментарий от Gary

данные сохраняться не должны

Хм... надо же. Действительно зануляются.

фрагментация уже реальной памяти

Это как раз решается виртуальной памятью.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

что не так-то? какой-нибудь лист и хешкарта - только в путь

next_time ★★★★ ()
Ответ на: комментарий от Gary

32-битные системы сейчас наверное редкость

Легаси. И пример, на котором грабли лучше видны.

question4 ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.