LINUX.ORG.RU

Найти где течет память в пистоне

 ,


1

4

Сабж. Кто-нибудь знает нормальный метод это сделать?

Есть скрипт, у которого ну прям очень явно течет память. При этом gc debug рапортует что усе собирается, а tracemalloc вообще считает что скрипт больше 10 метров не ест.

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

Пока думаю попробовать снять дамп через haystack посмотреть чем эта зараза сожрала полтора гига

upd: залез gdb, говорит 20 метров. система говорит 400. wtf?

★★★★★

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

Это реально фрагментация похоже, только непонятно где.

Да вроде понятно, внутренняя фрагментация кучи. Jemalloc как раз против такого. Я не знаю правда, насколько ситуация стала (и стала ли) лучше в последних glibc, но как минимум раньше под мелкие объекты оно юзало sbrk со всеми вытекающими.

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

Все вероятные сценарии уже давно пофиксили.

i-rinat ★★★★★
()

Может быть память «течет» из-за того, что процессы/потоки нормально не убиваются? Убери из скрипта все глобальные переменные. Возможно в памяти остается куча ссылок на одно и то же значение.

Я бы еще посоветовал проверить одинаковые ли версии интерпретаторов на проде и на тесте.

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

Версии одинаковые, а вот система разная (мак/дебиан). Течёт только дебиан. Глобальных переменных нет.

Судя по профайлеру не отдаёт память BytesIO, хоть оно и корректно закрыто. Хз, попробую truncate на него делать чтоль

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

после долгих копаний выяснил что в случае (второй, с сишечкой) где идет обработка картинки течет не BytesIO, на который я грешил, а собственно PIL, точнее метод save, а еще точнее - copy внутри него когда картинка read-only. Походу там в определённых случаях не закрывается буфер. Проверил несколько версий на линухе и маке - воспроизводится.

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

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

Вести с полей: походу течёт либо imaging core в pil, либо libtiff. Второе больно, его годами фиксят, видимо придётся multiprocessing и убивать процесс после обработки

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

Pillow разумеется, лень три буквы набрать было. Похоже течёт именно imaging core, потому что такое поведение повторяется и для png, и для других форматов.

Чую придётся пока подпереть костылем с перезапуском

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

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

Вангую что ТС говнокодер и не прочитал доки, а баг - не баг.

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

Там варианты либо with file либо close. Close пробовал, разница копеечная. Там скачет когда decode вызывается когда в сишку уходит

Тем более скачет не по 100м на 10млн, а прям куда жёстче

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

Вообще подозрительно. Pillow-таки юзает огромное число людей, и странно, что никто утечку не заметил причём в таких нехилых объёмах.

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

Хз, может делаю что-то совсем странное, посмотрю что в багрепорт ответят. Плюс я по большей части на tiff тестил, а это мягко говоря не самый распространённый формат, возможно что-то в его декодере не так (либо в самом libtiff который этот декодер вызывает). С png вроде как воспроизвести получается, но не настолько пристально смотрел.

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

Прямо из туториала. Берем N тифов, подряд вместе с каким-нибудь `memory_profiler` (без циклов чтоб было видно) вызываем такой кусок кода. Можно добавить gc.collect на каждый вызов

with open(source, 'rb') as f:
    Image.open(f).save('target.jpeg')

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

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

питон 3.7.9, Pillow 8.1.1+ (тестил 8.1.1-2 и 8.2.0)

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

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

with Image.open(source) as im:
    im.save('target.jpeg')
не проверял подробнее, можешь у себя затестить, может где в синтаксисе ошибка, но думаю как-то так надо. Потому что ты у себя корректно обрабатываешь with open, но некорректно обрабатываешь Image.open который +- то же самое делает

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

Пробовал. И открывать напрямую по пути файла без with file, и вручную вызывая close, и через Context Manager как ты написал.

Память жрёт decode внутри вызова load, что логично (собственно lazy-операция). Но вот только потом эту память никто не отдаёт, что не логично.

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

pillow uses a memory allocator that does not release image chunks back to the OS, but rather retains them and re-uses them for subsequent images

может я криво раздуплил ответ и это было предположение, а не утверждение, но собственно вот

В целом это нормально и логично, зачем каждый раз malloc делать если можно потом опять тот же кусок памяти заюзать. Но вот блин когда у тебя multi-tenant с небольшим перескоком по памяти, и когда WSGI-сервер пытается содержать N воркеров со своей памятью у каждого - в этом случае такое поведение прилично вредит и нифига не очевидно.

Собственно проблема у меня походу возникла как раз потому что каждый из N воркеров пытался держать по буферу вместо того чтоб этот самый буфер отпустить. То если если каждый хоть раз съел картинку на 100 метров, суммарно у них будет 100*N потребление, что очень больно

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

Так выноси в короткоживущие таски

спасибо капитан, вынесу.

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

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