parserALT
Страницы форума: ← Назад | 1 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 600 | Дальше →

Проблема/непонимание работы сборщика мусора или утечка памяти

#1nkostya
16.06.10 10:46
www.parser.ru → | ответить → | в избранное →

Проблема/непонимание работы сборщика мусора или утечка памяти

Столкнулся с проблемой возростающего потребления памяти и невозможности освободить ее. По сути:

Есть некий скрипт, который производит большое кол-во операций. В частности загружает с другого сервера XML файл, парсит и заносит в базу. И все это происходит в цикле.

Код примерно имеет следующую логику:
@main[]
^for[i](0;100){
	^import[]
}

@import[][messages]
^for[j](0;16){
	$messages[^ImportClassObject.import_by_part[$j]]
	$messages[]
	^Rusage:compact[]
}
$result[]


Класс ImportClass загружает данные с другого сайта, импортирует и возвращает импортированные данные. В переменные ничего не сохраняет, @OPTIONS = locals.

Проблема: Если замерять кол-во потребленной памяти до и после в @main, то оно не освобождается, а только растет.

Попытался подготовить данные по маленькому циклу для демонстрации проблемы:
[BEFORE 1] {"used" => "4524", "free" => "252", "ever_allocated_since_compact" => "2225.3", "ever_allocated_since_start" => "8130.47"}
	[BEFORE load] {"used" => "4536", "free" => "240", "ever_allocated_since_compact" => "2260.33", "ever_allocated_since_start" => "8165.5"}
	[AFTER load] {"used" => "14648", "free" => "3620", "ever_allocated_since_compact" => "57827.6", "ever_allocated_since_start" => "306299"}
	[AFTER import] {"used" => "24584", "free" => "7900", "ever_allocated_since_compact" => "14020.2", "ever_allocated_since_start" => "658280"}
[BEFORE 2] {"used" => "24608", "free" => "7876", "ever_allocated_since_compact" => "14933.9", "ever_allocated_since_start" => "659194"}
	[BEFORE load] {"used" => "24608", "free" => "7876", "ever_allocated_since_compact" => "14967.8", "ever_allocated_since_start" => "659228"}
	[AFTER load] {"used" => "31188", "free" => "1296", "ever_allocated_since_compact" => "28604.2", "ever_allocated_since_start" => "957702"}
	[AFTER import] {"used" => "34736", "free" => "5940", "ever_allocated_since_compact" => "10538.1", "ever_allocated_since_start" => "1.31113e+06"}
[BEFORE 3] {"used" => "34760", "free" => "5916", "ever_allocated_since_compact" => "11452.9", "ever_allocated_since_start" => "1.31204e+06"}
	[BEFORE load] {"used" => "34760", "free" => "5916", "ever_allocated_since_compact" => "11486.9", "ever_allocated_since_start" => "1.31207e+06"}
	[AFTER load] {"used" => "41064", "free" => "7804", "ever_allocated_since_compact" => "154914", "ever_allocated_since_start" => "1.60606e+06"}
	[AFTER import] {"used" => "44200", "free" => "4668", "ever_allocated_since_compact" => "28073", "ever_allocated_since_start" => "1.96236e+06"}


Правильно ли я понимаю, что "по идеи" на начало следующего цикла used памяти на начало каждого цикла в @main должно быть примерно около начального значения? ([BEFORE 1] "used" => "4524")

И скорее всего мне нужно покопать в сторону утечки памяти в классе ImportClass? Возможно есть какой-то известный баг, или особенность, которую не учитываю, т.к. свои классы я прочесал уже несколько раз.
#2Misha v.3
→ nkostya [#1] | 16.06.10 11:08
www.parser.ru → | ответить → | в избранное →
1. версия парсера?
2. что происходит с памятью при убирании ^compact? ([отя чего я спрашиваю? судя по приведённому логу собирается, причем много)

я боюсь, что без разборок в коде порекомендовать что-либо нереально.
#3nkostya
→ Misha v.3 [#2] | 16.06.10 11:20
www.parser.ru → | ответить → | в избранное →
Версия парсера - 3.4.0

У меня есть большое подозрение, что память перестает собираться если что-то загружается $file[^file::load[http://...]] но я не проверял еще. Не замечали совпадений?

С кодом проблема, его там очень много, там с десяток классов на импорт завязанно. Пока попробую еще покопать...
#4Misha v.3
→ nkostya [#3] | 16.06.10 11:39
www.parser.ru → | ответить → | в избранное →
попробуйте в отдельных тестах проверить освобождение памяти после:
- file::load
- xdoc::create
- sql/insert

если обнаружите утечки -- скорее всего будет поправлено в следующей версии.
#5moko
→ nkostya [#1] | 16.06.10 16:11
www.parser.ru → | ответить → | в избранное →

Что-то много памяти расходуется ...

Если посмотреть на числа:

[AFTER load]
"used" => "14648"
"ever_allocated_since_compact" => "57827.6" - note: странно, что это число может быть насколько больше used
"ever_allocated_since_start" => "306299" - а это, между прочим, 300Mb.

[AFTER import]
"used" => "24584"
"ever_allocated_since_compact" => "14020.2"
"ever_allocated_since_start" => "658280" - а это 650Mb.

На фоне 650Mb за одну итерацию загрузки потеря 10Mb (дельта между итерациями), то есть меньше 2% от аллоцированной памяти выглядит естественной "усушкой/утряской" - байты в памяти бывают похожи на адреса, поэтому GC может освобождать не все.

Другое дело, что 650Mb за цикл - это как-то много. Какой объем данных (в килобайтах/мегабайтах) обрабатывается за итерацию?

P.S. Еще один возможный источник утечек - какие-нибудь глобальные переменные в используемых классах (особенно хеши).
#6nkostya
→ moko [#5] | 16.06.10 16:27
www.parser.ru → | ответить → | в избранное →
процесс при большом кол-ве итераций в main вырастает до 1,4 Гб и более.

Загружаемый файл - около 2-4 КБ. порядка 200 записей.

Процесс преобразования примерно такой
text -> xml -> hash of hash -> hash of objects (в объектах хранится хеш) -> save to db (и множество операций по преобразованию)

Глобальные переменные есть, но их не так много. Везде пытаемся работать с хешами, даже в классах храним данные в хешах. С этим есть определенная сложность?
#7Misha v.3
→ nkostya [#6] | 16.06.10 17:06
www.parser.ru → | ответить → | в избранное →
кажется не на то обратили внимание:
Костя подсказывает, что между before load и after load съелось ~300 МБ. как такое может быть, если там всего-то загрузился 4-КБ файл? (или 200 файлов? да не суть, всё равно получается 800 КБ, а не 300 МБ)
#8nkostya
→ Misha v.3 [#7] | 16.06.10 19:58
www.parser.ru → | ответить → | в избранное →
BEFORE load - на начало цикла
BEFORE _request API - до вызова загрузки с сайта
AFTER _request API - после загрузки

итого на загрузку ушло 11200 - 4508 = 6684 (под загрузкой здесь подразумевается: подготовка запроса (он хитрый), получение данных с сервера + преобразование в xml) Получение происходит из тветера (http://api.twitter.com/1/statuses/user_timeline.xml?count=200) Много уходит на преобразование в xml такого маленького документа?

AFTER xml2hash - преоразование xml документа в хеш. Здесь и возрастает ever_allocated_since_start до бешенных размеров (функцию привожу ниже). Не вижу в ней откуда такой рост расхода памяти :(

AFTER import - из xml генерируются объекты и сохраняются в БД. Здесь тоже большой расход. Но я тоже не понимаю отчего такое. Может ли быть проблема в 64 битной версии парсера? Или в работе с хешами, т.к. по факту каждый объект, который создается - хранит только хеш данных и имеет с десяток методов (глубина наследования - 3 класса: Message -> ActiveMode -> Model).

[BEFORE load] {"used" => "4508", "free" => "268", "ever_allocated_since_compact" => "2200.77", "ever_allocated_since_start" => "8103.62"}
		[BEFORE _request API] {"used" => "4516", "free" => "260", "ever_allocated_since_compact" => "2233.22", "ever_allocated_since_start" => "8136.08"}
		[AFTER _request API] {"used" => "11200", "free" => "2508", "ever_allocated_since_compact" => "9954.66", "ever_allocated_since_start" => "15857.5"}
		[AFTER xml2hash] {"used" => "14024", "free" => "4264", "ever_allocated_since_compact" => "57555.8", "ever_allocated_since_start" => "304741"}
[AFTER load] {"used" => "14512", "free" => "3776", "ever_allocated_since_compact" => "59245.5", "ever_allocated_since_start" => "306431"}
	[AFTER import] {"used" => "25168", "free" => "7352", "ever_allocated_since_compact" => "20328.9", "ever_allocated_since_start" => "669705"}
[BEFORE load] {"used" => "25392", "free" => "7128", "ever_allocated_since_compact" => "21218.2", "ever_allocated_since_start" => "670594"}
		[BEFORE _request API] {"used" => "25396", "free" => "7124", "ever_allocated_since_compact" => "21250.5", "ever_allocated_since_start" => "670626"}
		[AFTER _request API] {"used" => "32552", "free" => "8160", "ever_allocated_since_compact" => "29154", "ever_allocated_since_start" => "678530"}
		[AFTER xml2hash] {"used" => "31844", "free" => "8868", "ever_allocated_since_compact" => "16770.5", "ever_allocated_since_start" => "967607"}
[AFTER load] {"used" => "31928", "free" => "8784", "ever_allocated_since_compact" => "18459.7", "ever_allocated_since_start" => "969296"}




Код функции преобразования xml -> hash

##############################################################################
@_xml2hash[xNode]
	^switch($xNode.nodeType){
		^case(1){
			$result[^hash::create[]]
			
			$list[^xNode.select[*]]
			
			^switch(true){
				^case($xNode.nodeName eq "ids"){
					^for[i](0;$list-1){
						$node[$list.$i]

						$result.[$i][^_xml2hash[$node]]
					}
				}
				
				^case(^xNode.getAttribute[type] eq "array"){
					^for[i](0;$list-1){
						$node[$list.$i]

						$result.[$i][^_xml2hash[$node]]
					}
				}
				
				^case[DEFAULT]{
					^for[i](0;$list-1){
						$node[$list.$i]

						$result.[$node.nodeName][^_xml2hash[$node]]
					}
				}
			}
			
			^if(!$list){
				$result[^xNode.selectString[string(.)]]
			}
		}

		^case(3){
			$result[$xNode.nodeValue]
		}

		^case(9){
			$result[^_xml2hash[$xNode.documentElement]]
		}

		^case[DEFAULT]{
			^throw[...;...]
		}
	}
	
	^Rusage:compact[]
#end @_xml2hash[]


Никто не тестировал расход памяти на создание хеша, расход памяти на копирование хеша, расход памяти на создание класса с хешем?
#9nkostya
→ Misha v.3 [#7] | 16.06.10 20:01
www.parser.ru → | ответить → | в избранное →

Загружается 1 файл

#10Vint
→ nkostya [#8] | 16.06.10 22:20
www.parser.ru → | ответить → | в избранное →
А можешь вложить xml-ответ с сайта твиттера? Ровно такого "маленького документа". Требуется регистрация в api, а это лень.
Поиграюсь ради интереса.

Думаю, стоит смотреть в сторону xslt-преобразования для разбора xml. Разбор дом-методами в парсере -- действительно ресурсоёмко.
#11Vint
→ Vint [#10] | 16.06.10 22:44
www.parser.ru → | ответить → | в избранное →

Так, я сам нашёл пароль:-)

Итоги маленького теста.

xml от твиттера весит ~ 64 кб.

Данные по расходу памяти (вывожу только $status:memory.ever_allocated_since_compact):
1. парсер пришёл в начало @main[] -- mu: 164.992
2. загрузил xml с диска в память (как объект) -- mu: 836.508
3. для каждого узла /statuses/status (28 итераций) выполнил @_xml2hash -- mu: 6730.11


^memory:compact[] в тесте не использовался ни разу.
#12nkostya
→ Vint [#10] | 16.06.10 22:51
www.parser.ru → | ответить → | в избранное →

http://api.twitter.com/1/statuses/user_timeline.xml?count=200&user_id=12 - без регистрации в API и на сайте

#13nkostya
→ Vint [#11] | 16.06.10 22:53
www.parser.ru → | ответить → | в избранное →

Мало итераций

Нужно с большим кол-вом сообщений пользователя взять, чтобы 200 за раз грузилось. Тогда и рост больший будет. А с чего он там такой...
#14nkostya
→ Vint [#10] | 16.06.10 22:59
www.parser.ru → | ответить → | в избранное →

Т.е. вы предлагаете XSLT трансформацией в некий tab-delimited формат а потом парсером? Быстрее будет?

#15Vint
→ nkostya [#13] | 16.06.10 23:04
www.parser.ru → | ответить → | в избранное →
Да, вот по этому урлу http://api.twitter.com/1/statuses/user_timeline.xml?count=200&user_id=12 получилось:
196 итераций и mu: 194306 (сильно!)
#16Vint
→ nkostya [#14] | 16.06.10 23:05
www.parser.ru → | ответить → | в избранное →

Именно

Либо по табличкам, либо как некий ход конём -- сформировать в xslt структуру хеша (парсерный код), а потом его process.
#17Maxx
→ nkostya [#14] | 17.06.10 00:32
www.parser.ru → | ответить → | в избранное →

tab-delimited можно и сразу в базу через LOAD DATA

#18Misha v.3
→ nkostya [#8] | 17.06.10 02:21 / 04:02
www.parser.ru → | ответить → | в избранное →

думаю, что тут проблема в xpath

я сам никогда не использую xpath в сложных ситуциях. обойти всё DOM методами (firstChild, nextSibling) будет существенно менее ресурсоёмко.

как выяснилось, «Проблема/непонимание работы сборщика мусора или утечка памяти [аналогично]» :)

P.S. в changelog libxml вижу: Fix memory leak in xmlXPathEvalExpression(). можете попробовать собрать парсер с этой последней версией либы.
P.P.S. я попробовал с последней libxml -- расход памяти без кардинальных изменений.
#19Misha v.3
→ Vint [#15] | 17.06.10 03:14 / 03:43
www.parser.ru → | ответить → | в избранное →

аналогично

если использовать «Проблема/непонимание работы сборщика мусора или утечка памяти» (убрал вызов compact и определил локальные переменые), то результаты такие (Time, BL и KB -- это то, что возвращаем Erusage):
Time: 1031.25 ms
BL: 12668
KB: 13152
ever_allocated_since_compact: 161322

то-же, но с явным определением result:
Time: 968.75 ms
BL: 9560
KB: 10128
ever_allocated_since_compact: 158294

если переписать метод на использование DOM (см. ниже. я использовал Node:foreachChild, который делает кучей ненужной для данного случая работы), то результаты такие:
Time: 515.625 ms
BL: 7824
KB: 8436
ever_allocated_since_compact: 22595.8

почувствуйте, как говорится, разницу. особенно в ever_allocated_since_compact...

@USE
Erusage.p
Node.p


@main[]
$x[^xdoc::load[x.xml]]
^memory:compact[]
^Erusage:measure{
	$h[^_xml2hash[$x]]
}[v]
#^dstop[$h]

Time: $v.time ms<br />
BL: $v.memory_block<br />
KB: $v.memory_kb<br />
ever_allocated_since_compact: $status:memory.ever_allocated_since_compact<br />

@_xml2hash2[xNode][i;result;node]
	^switch($xNode.nodeType){
		^case(1){
			$result[^hash::create[]]
			$i(0)
			^if($xNode.nodeName eq "ids" || ^xNode.getAttribute[type] eq "array"){
				^Node:foreachChild[$xNode;;node;]{
					$result.[$i][^_xml2hash2[$node]]
					$i($i+1)
				}
			}{
				^Node:foreachChild[$xNode;;node;]{
					$result.[$node.nodeName][^_xml2hash2[$node]]
					$i($i+1)
				}
			}
			^if(!$i){
				$result[$xNode.firstChild.nodeValue]
			}
		}

		^case(3){
			$result[$xNode.nodeValue]
		}

		^case(9){
			$result[^_xml2hash2[$xNode.documentElement]]
		}

		^case[DEFAULT]{
			^throw[...;...]
		}
	}
#end @_xml2hash2[]


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

xml2hash:
$.0[
		$.created_at[Wed Jun 16 15:04:36 +0000 2010]
		$.id[16312661011]
		...
		$.in_reply_to_screen_name[void]
		$.contributors_enabled[
			$.id[12]
			$.name[Jack Dorsey]


xml2hash2:
$.0[
		$.created_at[Wed Jun 16 15:04:36 +0000 2010]
		$.id[16312661011]
		...
		$.in_reply_to_screen_name[void]
		$.user[
			$.id[12]
			$.name[Jack Dorsey]


разница в 3 строке снизу (contributors_enabled/user). судя по xml должно быть user, хотя может я не понял исходную задумку.
#20Vint
→ Misha v.3 [#19] | 17.06.10 03:49
www.parser.ru → | ответить → | в избранное →
Локальный result у меня также выиграл 4 мб.

С foreachChild интересно получается. Надо будет последнюю версию либы собрать, может там xPath действительно хорошо поправили.

Ради интереса я всё-таки реализовал url="http://www.parser.ru/forum/?id=72982"]озвученный вариант с process[/url]:
_xml2hash:
Time: 0.579 s
MU: 187508 (локальный result)

xslt (прямыми руками*) + process + всякие хитрости:
Time: TT: 0.063 s
MU: 22245

* если руки не прямы, то память легко растёт до 90 мб :-)

P.S.: конечно же, process не стоит применять, это как минимум опасно, а если защищаться (tainting и замены через match), то выйдет дорого по времени и памяти на таком объёме данных.
А вот xslt оказался очень быстрым (опять же, с прямыми руками:-).
#21Misha v.3
→ Vint [#20] | 17.06.10 04:00 / 04:51
www.parser.ru → | ответить → | в избранное →

локальный result сразу выкидывает все whitespace

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


новая либа проблему не решила, я уже попробовал %-)
возможно проблема в неэффективных парсерных обёртках для xpath. надо будет смотреть.


я для подобных задач (xml->hash) постоянно пользуюсь DOM-ом (на рессурсоёмкость xpath наступил давным давно). да и xpath тут по сути не нужен, ведь тут простой обход дерева.


в приведённом ранее методе, использующем DOM операции можно сделать ещё одну небольшую оптимизацию (на обсуждаемом xml-е выигрывается ещё примерно 1 МБ):
-	^if($xNode.nodeName eq "ids" || ^xNode.getAttribute[type] eq "array"){
+	^if($xNode.nodeName eq "ids" || ($xNode.attributes && ^xNode.getAttribute[type] eq "array")){


или даже так (знаю, что это не соответствует тому, о чем говорится в документации):

-	^if($xNode.nodeName eq "ids" || ^xNode.getAttribute[type] eq "array"){
+	^if($xNode.nodeName eq "ids" || $xNode.attributes.type.nodeValue eq "array"){
#22nkostya
→ Vint [#16] | 17.06.10 16:48
www.parser.ru → | ответить → | в избранное →

process быстрее будет чем чтение tab-delimited?

#23nkostya
→ Maxx [#17] | 17.06.10 16:51
www.parser.ru → | ответить → | в избранное →

В моем случае LOAD DATA не поможет, т.к. много операций по подготовке данных и без них не обойтись :(

#24Vint
→ nkostya [#22] | 17.06.10 22:32
www.parser.ru → | ответить → | в избранное →

не уверен

С process'ом придётся повозиться, проверил.
tab-delimited безопаснее и быстрее.

Насколько понимаю задачу: надо из xml выбрать лишь несколько полей и положить их в БД, или передать в конструктор объекта, а потом уже сохранить его поля в БД.
В обоих случаях нужны сериализованные данные и необходимости получить именно хеш-структуру из xml нет.
#25Misha v.3
→ nkostya [#22] | 18.06.10 08:11
www.parser.ru → | ответить → | в избранное →

с tab-delimited у вас тоже будут небольшие проблемы...

табы и ентеры для tab-delimited будет выдавать xsl. он-же будет выдавать содержимое, которое было в исходном xml-е.

но вот отличить разные переводы строки будет невозможно, т.к. у них после transform будет одинаковый taint.

т.е. придётся немножко корячиться, выдавая вместо них какие-нить сигнатуры, а потом натравливая taint с replace-ом.

лично я в сторону tab-delimited смотрел бы только если бы было возможно сделать LOAD DATA INFILE. а иначе использовал бы уже имеющийся код, просто убрав xpath из обхода дерева. в этом случае и compact будет лучше работать, т.к. ему не придётся в сотнях мегабайт ковыряться.
#26Vint
→ Misha v.3 [#25] | 18.06.10 14:05
www.parser.ru → | ответить → | в избранное →

Но в любом случае xslt будет быстрее и по памяти нормально

, что и показал мой тест с process.

taint и replace в разумном подходе не осложнят ситуацию, особенно, если результирующих данных после трансформации будет сильно меньше, чем в исходном xml. А их точно будет меньше хотя бы из-за tab-delimided формата:-)
Страницы форума: ← Назад | 1 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 600 | Дальше →