Верстакфорум практиков
рекламаiprazon: приватные серверные адреса IPv4 и SOCKS5, безлимитный трафик, бесплатный тест до 2 часов
ФорумРазбор ошибок

Память течёт на длинном прогоне

ilya.works
ilya.works
Участник
сообщений 305
с ноя
22 декабря, 17:47первое сообщение

Список на четыреста тысяч строк, прогон рассчитан на ночь. К утру процесс убит по памяти, пройдено чуть больше половины.

Мерил занятую память раз в минуту. Старт 210 МБ, через час почти 900, дальше ровно вверх до потолка.

сколько памяти держит процесс
сколько памяти держит процесс

Питон 3.11, requests, разбор через lxml, потоков двадцать. Куда смотреть?

hexdump
hexdump
Знаток
сообщений 940
с июн
1 мая, 08:04#2

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

Выкладывай, что у тебя объявлено снаружи цикла: списки, словари, кэши, сеанс requests.

ilya.works
ilya.works
Участник
сообщений 305
с ноя
8 октября, 11:21#3
hexdump: что живёт дольше одной задачи

Снаружи цикла список rezultaty, туда кладу разобранную карточку, в конце пишу файл. Ещё словарь vidano под проверку повторов и один сеанс на все потоки.

Список к утру и правда огромный, но там строки. Откуда шесть гигабайт?

grepwalker
grepwalker
Знаток
сообщений 1330
с мая
15 марта, 14:38#4

Шесть гигабайт из строк набрать тяжело, а вот из ссылок на разметку легко. Посмотри внимательно, что именно ложится в rezultaty. Если туда попадают объекты lxml, каждый такой элемент тянет за собой весь документ: одна ссылка держит в памяти страницу на полтора мегабайта, четыреста тысяч ссылок держат всё, что ты за ночь скачал.

Проверяется за десять минут. Ставишь два снимка памяти в разных точках прогона и смотришь разницу.

import tracemalloc
tracemalloc.start(10)

# после первой тысячи задач snap1 = tracemalloc.take_snapshot()

# после десятой тысячи snap2 = tracemalloc.take_snapshot() for st in snap2.compare_to(snap1, "lineno")[:12]: print(st) ```

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

Словарь vidano посмотри отдельно. Четыреста тысяч ключей это ещё терпимо, но если ты кладёшь туда кусок страницы вместо короткого артикула, вот тебе и гигабайты.

Ирина Пахомова
Ирина Пахомова
Новичок
сообщений 280
с янв, второй сезон
22 августа, 17:55#5

Добавлю место, про которое забывают: журнал. Когда logging пишет через очередь, а разгребает её никто, очередь пухнет вместе с прогоном.

И проверьте, чем закрываются ответы. При stream=True тело держится до полного чтения, брошенный ответ остаётся висеть в пуле соединений вместе со своим буфером.

pingpong
pingpong
Новичок
сообщений 105
с июн, второй сезон
1 января, 08:12#6

а gc.collect() каждую тысячу задач не поможет? я так у себя делал

hexdump
hexdump
Знаток
сообщений 940
с июн
8 июня, 11:29#7

pingpong, сборщик уберёт только то, на что уже никто не ссылается. Пока карточка лежит в общем списке, ссылка живая, и сборщик пройдёт мимо.

Он выручает ровно в одном случае: кольцевые ссылки на объектах со своим __del__. Всё остальное это лечение симптома.

ilya.works
ilya.works
Участник
сообщений 305
с ноя
15 ноября, 14:46#8

Снял снимки, наверху действительно lxml/etree. Я складывал в список кортеж, и третьим элементом там лежал найденный узел таблицы, из которого потом дёргал цену на выгрузке. Про то, что узел держит весь документ, я и не подозревал.

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

Прошло девять часов, память стоит на 260 МБ и не двигается. Список пройден целиком.