LINUX.ORG.RU

Не до конца освобоздается память программой (си и Glib)

 , ,


0

2

Изучаю списки GList. И решил тщательно рассмотреть освобождение памяти в конце использования Glist. И заметил что память не до конца освобождается. Текст программы - check_glib.c

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

#define N 5000
#define LEN 24000

int main(int argc, char **argv)
{
	g_print("name = %s\n\n", argv[0]);
	
	gchar * command = NULL;
	command = g_new(gchar, 256);
	sprintf(command, "perl ./script.pl %s\n", argv[0]);

	GList *list = NULL;
	gint i = 0;
	gint j = 0;
	gint l = 0;
	gint *array;
	
	for (i=0; i<N; i++) {
		
		fprintf(stderr, " %d ", i+1);
		list = NULL;
		
		for(j=0; j<=i; j++) {
			array = g_new(gint, LEN);
			for(l=0; l<LEN; l++) {
				array[l] = l;
			}
			list = g_list_append(list, (gpointer)array);
		}
		sleep(1);
		fprintf(stderr, "| full: ");
		system(command);
		
		g_list_free_full (list, g_free);
		
		sleep(1);
		fprintf(stderr, "| free: ");
		system(command);
		fprintf(stderr, "\n");
	}
	
	g_free(command);
	return 0;
}
Программа выше в цикле выделяет память и затем освобождает ее. С каждым новым циклом памяти выделяется больше. При этом производится мониторинг занятой памяти.

Для мониторинга написал perl скрипт script.pl. Скрипт берет вывод утилиты с ключами ps aux - находит параметры RSS (Resident Set Size) и VSS (Virtual Set Size) для запущенной нами программы и только их выводит на экран.

Текст script.pl:

#!/usr/bin/perl

use strict;
use warnings;

# берем вывод утилиты ps и разбиваем на строки
my @ls1=split(/\n/,`ps aux | grep $ARGV[0]`);

# выбираем строчку вывода ps, которая не содержит слов grep и perl
my $line;
foreach my $tline (@ls1) {
	if ( !($tline =~ m/grep/) and !($tline =~ m/perl/)) {
		$line = $tline;
	}
}

$line=~s/\s+/ /g; # удаление дублирующих пробелов
my @ls2=split(/ /,$line);
print STDERR "V $ls2[4] R $ls2[5] ";

И makefile для сборки:

# basic GTK+ app makefile
SOURCES = check_glib.c
PACKAGE = check_glib
OBJS    = ${SOURCES:.c=.o}
CFLAGS  = `pkg-config gtk+-3.0 --cflags`
LDADD   = `pkg-config gtk+-3.0 --libs`
CC      = gcc -Wall

all : ${OBJS}
	${CC} -o ${PACKAGE} ${OBJS} ${LDADD}

.c.o:
	${CC} ${CFLAGS} -c $<

clean:
	rm *.o ${PACKAGE}

# end of file

Сейчас покажу часть вывода программки у меня:

 46 | full: V 16156 R 6540 | free: V 12032 R 2592 
 47 | full: V 16344 R 6540 | free: V 12032 R 2592 
 48 | full: V 16344 R 6804 | free: V 12032 R 2592 
 49 | full: V 16532 R 6804 | free: V 12032 R 2592 
 50 | full: V 16532 R 6804 | free: V 12032 R 2592 
 51 | full: V 16720 R 7068 | free: V 12032 R 2592 
 52 | full: V 16720 R 7068 | free: V 16720 R 7248 
 53 | full: V 16908 R 7248 | free: V 16816 R 7340 
 54 | full: V 17004 R 7340 | free: V 16816 R 7376 
 55 | full: V 17004 R 7376 | free: V 16816 R 7376 
 56 | full: V 17188 R 7596 | free: V 16816 R 7376 
Вывод содержит 3 колонки разделенных вертикальным линией. 1 колонка номер шага, 2 - вывод RSS и VSS когда массивы заполнены. 3 - вывод RSS и VSS после освобождения памяти.

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

В общем меня этот момент с памятью удивил и заинтересовал. Кто-нибудь может объяснить что происходит с памятью?

★★

Последнее исправление: CYB3R (всего исправлений: 3)
Ответ на: комментарий от FIL

ну как минимум:

#!/usr/bin/perl -W

#!/usr/bin/perl

use strict;
use warnings;

my $name = $ARGV[0];

ненужное присваивание

my $grep = «grep»;
my $perl = «perl»;

это вообще должно быть константами

да и вообще, у ТС-а говнокод какой-то

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

Ну так и говори, а то я тебе не правильно понял...
p.s. perl не знать

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

g_list_free_full (list, g_free); // Полная очистка списка: g_free (аналог free) выполняется для каждого элемента списка.

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

Перл здесь как вспомогательный элемент. К нему претензий нету Утилита ps много лишнего выводит - вот и решил почистить вывод используя perl. В принципе можно перл убрать и самому проверять память.

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

сдается мне что в этом треде мы увидим только экспертов по DNA

anonymous
()

Насколько я понял:

  • ты создаешь список указателей на массивы;
  • память на массивы ты выделяешь сам, по только тебе известной длине;
  • g_list_free_full() чистит только список этих указателей, но не сами массивы;
  • g_list_free_full() ничего не знает про длину твоих массивов;
  • массивы за собой ты должен подтереть сам, так как ты один знаешь длину
hibou ★★★★★
()
Ответ на: комментарий от hibou

g_list_free - чистит только списки указателей - как я понял. А g_list_free_full() еще вызывает отдельную функцию для очистки https://developer.gnome.org/glib/stable/glib-Doubly-Linked-Lists.html#g-list-...

А как правильно подтереть массивы? А команда free узнает длину массива?

neon1ks ★★
() автор топика

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

ananas ★★★★★
()
        command = g_new(gchar, 256);
        sprintf(command, "perl ./script.pl %s\n", argv[0]);

превращается в

        command = g_strdup_printf ("perl ./script.pl %s\n", argv[0]);

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

Для освобождения памяти не нужно знать длину, её знает «менеджер памяти»(в смысле тот, кто даёт тебе интерфейс malloc/free и ему подобных). А в остальном, возможно, ты прав.

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

Нет, не знает. С выделением и тем более с освобождением памяти у тебя полный misunderstanding. Разберись детальней.

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

У него там разбираться надо. Есть функции, которые массивы освобождают, но они подразумевают, что массив нулем кончается. Это не его случай.

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

Кроме того, вот смотри, если открыть справку по g_new, то там читаем такое:

Allocates n_structs elements of type struct_type . The returned pointer is cast to a pointer to the given type.

То есть, похоже у него массив-то не интов, а массив указателей на инты. То есть и освобождать он должен будет каждый этот инт по-отдельности.

hibou ★★★★★
()

и еще тебе хинт для обучения - попробуй увеличить размер стека

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

Можно заменить на

CFLAGS  = `pkg-config glib-2.0 --cflags`
LDADD   = `pkg-config glib-2.0 --libs`
Но большой роли это не играет.

https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-p...
https://developer.gnome.org/glib/stable/glib-Spawning-Processes.html

Большое спасибо за ссылки

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

g_new это обертка для malloc. g_new возвращает указатель на массив состоящих из n_structs указанного типа struct_type.

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

Запустил программу через valgrind

valgrind --leak-check=full --show-leak-kinds=all ./check_glib

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

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

например потому, что зарезервировать в юзерспейсе быстрее, чем заново аллоцировать через сисвызов

anonymous
()

FIL, hibou: тут все ок, g_new() это хипстерский malloc(), а g_list_free_full(l, f) делает f() для каждого l->data, и g_free() для каждого l. На удивление правильно для чувака, вызывающего getrusage() через sh->perl->ps.

Кто-нибудь может объяснить что происходит с памятью?

По некоторым причинам память не возвращается операционке, а помечается как свободная уже внутри процесса. Почему так — зависит от внутренностей вызываемых функций, как glib, так и glibc, и является деталями реализации.

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

рано тебе предъявлять утечки к системным либам... с инструментарием не знаком, принципов работы менеджеров памяти не знаешь... какими-то костыликами подкрепил свое псевдознание и бегом на лор, хакир блин

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

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

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

Это «открытый» вопрос. Если не можешь объяснить, так и скажи. Я не наезжаю и сам не в курсе, почему именно так «с 52 шага».

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

51 | full: V 16720 R 7068 | free: V 12032 R 2592
52 | full: V 16720 R 7068 | free: V 16720 R 7248

Можно условно выделить 3 столбца разделенные вертикальной линией. В первом столбце - номер шага в цикле. Два остальных столбца - значения памяти в двух состояниях.

В каждом шаге цикла в начале выделяется память для массивов, которые заполняются числами.Это состояние назовем full. Далее память освобождается После освобождения - состояние free.

Для каждого состояние определю параметры RSS (Resident Set Size) и VSS Virtual Set Size) - сокращенно обозначил у себя просто R и V соответственно. Значения в Кб.

В каждом новом шаге цикла - памяти выделяется больше.

Выше выделил две строчки, на которых видно, что на 52 шаге цикла память не отдалась обратно системе, а скорее всего зарезервировалась программой для дальнейшего использования.

Уже писал, меня этот факт удивил и заинтересовал. Это не всегда срабатывает. Есть немного измененные версии программ, где этого нету. Интересно, от чего это зависит и можно ли как-нибудь управлять этим.

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

Резервирование памяти - как я понял частое явление. В linux наверное аналогично должно быть.

http://habrahabr.ru/post/111834/

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

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

Решение было простым даже до исправления проблемных клиентов и защиты от переполнения буферов — возраст “старых” транзакций был временно ограничен 30 секундами, и этого было вполне достаточно для своевременной вычистки буферов при данной нагрузке, с большим запасом. Но диагностика и поиск неисправности отняли немалое время, главным образом из-за некорректной статистики в логах.

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

Если при выделении на низком уровне используется brk()/sbrk() - так и должно быть, если последний выделенный регион памяти по разным причинам ещё используется

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