LINUX.ORG.RU

C++. Предсказуемость поведения менеджера памяти.


0

1

Проверять результат выполнения new/malloc и т.п. - это конечно полезно, но не так прикольно. Не переключайте канал!

Софтина изредка в несколько потоков вызывает new(), прося что-то в районе 1...2 МБ памяти. Подобные запросы случаются одновременно из 4 потоков. Результат вызова не проверяется. Железных ядер - 4 (на всех нижеописанных платформах).

На тестовой платформе с linux 3.4.9 с 32 Гб ОЗУ работала безупречно.

На тестовой платформе с win-7 с 16 Гб ОЗУ иногда валилась. Валилась с одинаковой вероятностью и на виртуалке с 4 Гб ОЗУ.

Казалось бы - 16 гигов! Параллельно фотошопов не загружено. Если смотреть на стандартный «график использования ресурсов», то уровень занятости памяти валяется в нижних 20%.

Почему может так происходить, что при наличии кучи свободной памяти изредка происходят такие обломы? Ясно, что в таком топике на форуме невозможно объяснить и понять логику такой хитрой штуки, как менеджер памяти какой-либо ОС. Но хотя-бы какими его свойствами это может объясняться? Какие причины гипотетически тут могут быть? Он работает асинхронно от вызовов выделения памяти? Вызов на выделение памяти не блокирующий и может произойти в «неудобное время» (идёт процесс переупорядочения свободных блоков) и будет отклонён? Занятость диспетчера памяти своими служебными задачами рассматривается как отказ, равнозначный отсутствию свободной памяти? Почему вызывающий поток не будет заблокирован, чтобы дождаться окончания служебных процедур, если они кратковременные?

Ответ на: комментарий от Anon

А вот если ты ей сделаешь memset(mem, 0, size), и это пройдет без проблем, то все ОК

да ну? а как же overcommit, особенно в виртуалке? Как щаз помню забил нулями 80 гигабайт из 8 доступных :)

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

Версия о том, что у меня кривой код уже прозвучала. Можно переходить к другим версиям, если они есть. Не надо так долго топтаться на одной.

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

Память виртуальная, но если ОС тебе её выделила, то она должна убицца, но в крайнем случае начать юзать своп при обращении к этой памяти. А говорить о том, что часть выделенной памяти (особенно виртуальной) вдруг станет не моей и я получу SEGSEGV - это какой-то бред )

Т.е. вот это ты писал не для того, чтобы тебе кто-то ответил, как оно на самом деле, а высказывал экспертное, непогрешимое заключение. Ооок.

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

Другий версий не будет, пока есть эта. И код-то покажи.

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

Я верю, но речь не идёт о том, как сделать серверу хорошо.

Никто не мешает с моим мнением поспорить.

geekless ★★
()

А теперь представь, что я знаю правильный ответ, но из-за твоего епони на аватарке, отвечать не стану.

nanoolinux ★★★★
()
Ответ на: Пруф или не было!11 от geekless

запусти и сообщи, что ты видишь

попробуй надеть шапочку из фольги и сообщи, если результат изменится

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

void writeInt(unsigned char* byteArray, unsigned long longInt, unsigned long int offset) {
	byteArray[offset+0] = (longInt >> 24) & 0xFF;
	byteArray[offset+1] = (longInt >> 16) & 0xFF;
	byteArray[offset+2] = (longInt >> 8) & 0XFF;
	byteArray[offset+3] = (longInt & 0XFF);
}

unsigned long int readInt(unsigned char* byteArray, unsigned long int offset) {
	unsigned long int result;

	result  = ((unsigned long int) byteArray[0+offset]) << 24;
	result |= ((unsigned long int) byteArray[1+offset]) << 16;
	result |= ((unsigned long int) byteArray[2+offset]) << 8;
	result |= ((unsigned long int) byteArray[3+offset]);

	return result;
}

int main(void)
{
	unsigned char *mem = NULL;
	unsigned long int size, i, x;
	unsigned int bsize = 1048576;
	int success = 1;

	size = 0;
	do
	{
		printf("Requested %i MB...", size);
		mem = malloc(size*bsize);
		if (mem)
		{
			for (i=0; i < (size*bsize)/4; i=i+4) {
				writeInt(mem,i,i*4);			
				//printf("write OK, ");
				
				x = readInt(mem,i*4);
				if (x==i) {
					//printf("read OK\n");
				} else {
					success=0;
					printf("read FAILED for index %i, value %i\n",i,x);
				}
			}
			if (1==success) {
				printf(" OK\n");
			} else {
				success=1;
				printf(" FAILED\n");
			}
			free(mem);

		}
		else
		{
			printf("Failed!\n");
		}
		size+=50;
	} while (NULL!=mem);
}
stevejobs ★★★★☆
()
Ответ на: комментарий от kiverattes

Вариант с повреждением структур аллокатора интересный, но я по этому поводу ничего не могу сказать, просто запомню как один из вариантов.

Под Valgrind-ом гонял? Он хорошо отлавливает ошибки работы с памятью. Под Линуксом они могут оставаться незамеченными, а вот под Виндой стрелять.

Про утечку я наверное косвенно уже написал - при наличии свободных гигабайт происходит проблема выделения 1...2 Мб.

Я так понял, что ты писал про свободную физическую память. А я про виртуальную.

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

Пацаны, не запускайте, это вирус! У меня комп сгорел, пишу с тарелки!

А что этот код должен доказывать, собственно?

Он будет жрать память, пока она не кончится, потом упадёт. Мне чтобы это проверить, нужна 64-битная система, которой у меня нет, но это и так ясно.

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

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

Описанный bad_alloc у меня ловится под visual studio 2010 в дебажном режиме в виндовозе в описанном std::vector<>::resize(). В линуксе уронить приложение никак не получается, чтобы я ни делал...

kiverattes ★☆
() автор топика
Ответ на: комментарий от geekless

Эдди, ответь ТСу, почему оверкоммит предпочтителен для сервера. Мне лень писать, я вкусный суп ем. :D

Я уже поел, но мне тоже лень работать посредником между гуголом и ТСом.

Anon
()

он только что отъел памяти больше, чем в cat /proc/meminfo и больше чем в «свойствах моего компьютера» в венде. И не упал. Чью память он жрет теперь?

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

Утечка виртуальной памяти? А как это посмотреть в винде? Я думал, если диспетчер задач винды показывает, что приложение выделило 1.5 Гб и ещё 2.5 Гб свободно (окно с зелёными графиками), то типа ещё есть куда прыгать. Особенно при 16 физических гигах и 64 битах, когда после выделения 1.5 гигов свободно очень дофига.

Хм, правда я забыл написать, что само приложение собрано как 32 битное. Щас меня будут бить. Я не говорю про 4, но гига 2-то должно выделить хотя-бы... )

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

1) диспетчер задач - врёт, ставь process explorer
2) 32-х битное приложение в венде получает по-умолчанию 2 гб vram. Потому, если диспетчер задач тебе показывает 1.5 для 32-х битного приложения то там может уже и все два быть.

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

Хм, правда я забыл написать, что само приложение собрано как 32 битное. Щас меня будут бить. Я не говорю про 4, но гига 2-то должно выделить хотя-бы... )

Для 32 битной платформы совершенно не важно сколько на машине реальной памяти: 16 или 32 Гб. Все упирается в адресное пространство, которое ограниченно 4 Гб, причем 1 или 2 Гб (в зависимости от ОС) сразу откусывается ядром.

Дальше, судя по твоим описаниям, ты быстренько фрагментируешь остатки адресного пространства постоянными перераспределениями, которые делает вектор при расширении и приложение закономерно приплывает...

anonymous
()
Ответ на: комментарий от geekless

Проверил на линуксе: при totalmem 800 мб падает на 600 мб

На винде всё еще подтверждается: оно пишет больше, чем totalmem+swap

У меня 12Гб рамы и своп отключен на всех дисках. Возможно, оно начинает юзать своп самостоятельно, без разрешения пользователя. Либо оно как-то сжимает память.

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

Блин, забыл про process explorer. Раньше под винду больше кодил, его юзал... Про 2Гб vram интересно. Были подозрения.

kiverattes ★☆
() автор топика
Ответ на: комментарий от stevejobs

Ты ведь понимаешь, что это фантастика.

Либо оно как-то сжимает память.

Чтобы MS реализовало zswap и не раструбило об этом на каждом углу? Сомнительно. Я бы сказал, практически исключено.

Возможно, оно начинает юзать своп самостоятельно, без разрешения пользователя.

Бинго!

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

Платформы все 64, само приложение собрано как 32. Забыл этот момент написать. Мне 32 нужно, это версия дистра такая )

kiverattes ★☆
() автор топика
Ответ на: комментарий от stevejobs

Никогда не видел сообщение в трее «тут память кончилась, щас свопа добавлю»? Еще в XP оно было...

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

можно получить 4, если включить large address aware (кажется так).

а лучше конпилять под 64 бита... или искать что жрёт и оптимизировать :)

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

Может. Ведь тебе дают виртуальную память, а реальной она становится лишь при обращении. Так что, пока ты по всему массиву выделенной памяти не пройдешься, ты не можешь сказать точно, что она у тебя есть

Пеши есчо!

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

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

Для ползователя это всё прозрачно и он не обязан явно вычислять каждый байт в выделенной памяти

А на кой ему что-то вычислять? А вот пройтись memset'ом для верности — необходимо.

Anon
()
Ответ на: комментарий от yoghurt

Он всё правильно написал, просто немного неточно. Память у тебя всегда есть, она ведь виртуальная. То, что физической у тебя нет - это как-бы детям понятно, виртуальная память же... А он написал так, словно у тебя виртуальная «не вся есть». Слегка путает, но он имел ввиду физическую.

kiverattes ★☆
() автор топика
Ответ на: комментарий от Anon

А вот пройтись memset'ом для верности — необходимо.

А какая тебе разница, будет твоя программа убита ядром во время memset или 10 миллисекунд позже? Если памяти нет, то её нет...

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

+1

Пройтись мемсетом - даже вредно. Только приближаешь свою кончину. А вдруг она на 1 мс позже станет свободной?

kiverattes ★☆
() автор топика

Ошибка в коде. Сделай минимальный падающий пример и покажи его.

anonymous
()
Ответ на: комментарий от geekless

Это если оптимизировать, то неделю. Ну и от задачи зависит. Можно ведь в некоторых случаях моделировать не дискретно (фотонами), а непрерывно (функцией освещенности).

Anon
()
Ответ на: комментарий от kiverattes

Утечка виртуальной памяти? А как это посмотреть в винде?

В обработчике исключения bad_alloc вызови GlobalMemoryStatusEx() и посмотри статистику. Делать это лучше поближе к месту генерации исключения, в идеале обернуть в try/catch тот самый std::vector<>::resize().

само приложение собрано как 32 битное

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

Если проблема таки в нехватке линейного пространства и это не утечка, то попробуй сказать линкеру /LARGEADDRESSAWARE. Тогда у приложения будет 4 ГБ адресного пространства на 64-битных виндах.

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

А толку то?

Да, в общем случае это корректней. И я даже так делаю иногда.

Смысла читать выделенную, непроинициализированную память - нет. И при записи в неё занулять её целиком тоже нет особого смысла - если, как ты говоришь, возникнут «ошибки» (какие? Сигнал получим?), то они возникнут всё равно что при занулении, что при обычной записи туда (а для чего мы, собственно, и выделяли память).

Вон посоны в глибц аллоцируют и не парятся: http://fossies.org/dox/glibc-2.18/strdup_8c_source.html

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

посоны в глибц аллоцируют и не парятся

Ну так это только Эддя считает себя умнее разработчиков libc и ядра вместе взятых. :)

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

bad_alloc может вызываться если кто-то где-то насрал в память, прогони под valgrind

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

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

Anon
()
Ответ на: комментарий от kiverattes

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

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

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

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