Уведомления

Группа в Telegram: @pythonsu

#1 Июнь 2, 2018 09:46:42

xzvf
Зарегистрирован: 2016-09-04
Сообщения: 12
Репутация: +  0  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

Всем привет. Столкнулся с таким поведением интерактивной оболочки ipython: экспериментирую с данными и мне иногда приходится делать вещи вроде np.random.random(100_000_000). После того, как ввожу данную команду в ipython в интерактивном режиме (без присваивания результата какой-либо переменной) системной памяти становится меньше на ~750 мб (os windows7, python 3.6.4 64-bit). При повторном вводе еще на -750 мб - в диспетчере задач эта память потребляется процессом python (не ipython). И так пока вся память не заканчивается, и я не получаю ошибку MemoryError. Для того, чтобы освободить память приходится перезапускать ipython.

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

Если вначале присваивать выражение требующее много памяти переменной, например a = np.random.random(100_000_000) и затем руками писать del a, тогда память корректно освобождается и в python, и в ipython. Но так делать неудобно по понятным причинам - часто для выражений не нужны никакие переменные, а интересует только их результат в repl.

gc.collect() в ipython тоже ничего не очищает.

В соответствии с этим у меня 2 вопроса:

1. Как привести поведение ipython хотя бы к поведению стандартного интерпретатора python, чтобы не было утечек и необходимости перезапускать ipython?
2. Можно ли как-то настроить стандартный python-shell так, чтобы после того как он вычислил выражение, то сразу же подчищал за ним память, если на него не ссылается ни одна переменная?

Отредактировано xzvf (Июнь 2, 2018 12:49:56)

Офлайн

#2 Июнь 2, 2018 23:16:22

xzvf
Зарегистрирован: 2016-09-04
Сообщения: 12
Репутация: +  0  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

Пока что нагуглились только вариант удалять кеш (_, _1, _3, … _N) вручную с помощью %xdel или полностью очищать неймспейс текущей конфигурации с помощью команды get_ipython().reset(), которая собирает (collect) все пользовательские переменные. Но это все не то.

Офлайн

#3 Июнь 2, 2018 23:35:12

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9873
Репутация: +  853  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

? (справка)

* Output caching system:

For output that is returned from actions, a system similar to the input
cache exists but using _ instead of _i. Only actions that produce a result
(NOT assignments, for example) are cached. If you are familiar with
Mathematica, IPython's _ variables behave exactly like Mathematica's %
variables.

The following GLOBAL variables always exist (so don't overwrite them!):
_ (one underscore): previous output.
__ (two underscores): next previous.
___ (three underscores): next-next previous.

Global variables named _<n> are dynamically created (<n> being the prompt
counter), such that the result of output <n> is always available as _<n>.

Finally, a global dictionary named _oh exists with entries for all lines
which generated output.

Используй присваивание. Зачем такие большие данные без присваивания?



Офлайн

#4 Июнь 3, 2018 00:08:02

xzvf
Зарегистрирован: 2016-09-04
Сообщения: 12
Репутация: +  0  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

>> Зачем такие большие данные без присваивания?

Без присваивания удобно, чтобы посмотреть сразу результат, а не использовать потом еще print() и подобное. Так я могу и в ide без ipython. Кроме того, лишние переменные мне в неймспейсе не особо нужны. И память занятую ими все равно придется освобождать вручную (del, %xdel, .collect).

random.random - приведен для примера, это может быть и ф-ция, которая применяется к такому большому датасету и возвращает 1 или 0. Или если ты хочешь взять срез с этого датасета, чтобы посмотреть какие-то данные - память расходуется в полном объеме, даже если это первые 10 элементов.

Сейчас вот как раз пробую настроить систему кеширования. Пока что получилось добиться следующего поведения - сохраняются и, соответственно, занимают память только последние 3 выражения (даже если они одинаковые). Для этого необходимо в конфигурационном файле ipython прописать: c.TerminalInteractiveShell.cache_size = 0. Но не совсем понятно, почему кеш все-таки не 0, а по факту получается 3. В доке указано, что все значения меньше 3х приводятся к нулю. Вероятно, это как раз остались _, __, ___.

Опять же их можно удалять вручную с помощью %xdel. Но хотелось бы все-таки как-то настроить ipython-shell так, чтобы в памяти ну хотя бы сохранялась только _, как это реализовано, например, в стандартном python-shell (не ipython). А в идеале избавиться и от этой переменной :-)

Отредактировано xzvf (Июнь 3, 2018 00:31:51)

Офлайн

#5 Июнь 3, 2018 03:58:20

xzvf
Зарегистрирован: 2016-09-04
Сообщения: 12
Репутация: +  0  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

Удалось добиться поведения стандартного python-shell заменой self.__ и self.___ на None в исходниках IPython/core/displayhook.py. Наверное, если еще немного повозиться, можно организовать и очистку памяти от последнего выражения (_), но пока и так уже лучше, чем было.

Офлайн

#6 Июнь 3, 2018 05:01:57

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9873
Репутация: +  853  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

xzvf
random.random - приведен для примера, это может быть и ф-ция, которая применяется к такому большому датасету и возвращает 1 или 0.
Кешируется только вывод. Какая функция тормозит, если вывода этих данных нет?

xzvf
Или если ты хочешь взять срез с этого датасета, чтобы посмотреть какие-то данные - память расходуется в полном объеме, даже если это первые 10 элементов.
Так в numpy не итераторы же, сначала создаётся всё полностью и потом для него создаётся срез.

К переменным _ __ ___ цепляется только то, что ты выводил в консоль, а не то, что ты там вообще создавал. Поэтому используй присваивание и ничего не будет цепляться к _ , потому что оно не будет выводиться при присваивании.

xzvf
Без присваивания удобно, чтобы посмотреть сразу результат, а не использовать потом еще print() и подобное.
Очень сомнительно, что ты сможешь просмотреть 100000000 элементов в выводе. Наверное, поэтому там и не сделано ничего, чтобы это можно было просматривать, чтобы это не кешировалось.



Отредактировано py.user.next (Июнь 3, 2018 05:08:02)

Офлайн

#7 Июнь 3, 2018 07:02:12

xzvf
Зарегистрирован: 2016-09-04
Сообщения: 12
Репутация: +  0  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

>> Кешируется только вывод. Какая функция тормозит, если вывода этих данных нет?

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

>> сначала создаётся всё полностью и потом для него создаётся срез.

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

>> К переменным _ __ ___ цепляется только то, что ты выводил в консоль, а не то, что ты там вообще создавал.

Я в курсе.

>> Поэтому используй присваивание и ничего не будет цепляться к _ , потому что оно не будет выводиться при присваивании.

Как я уже писал ранее, этот вариант не подходит. Меня устраивает даже вариант поведения стандартного python-интерпретатора. Там ведь все реализовано как надо, почему в ipython это поведение не унаследовали?

>> Очень сомнительно, что ты сможешь просмотреть 100000000 элементов в выводе

Я хочу просмотреть первые 10, или с 10000982 элемента по 10001182. А память будет резервироваться как для изначального набора данных. 2-3 раза посмотрел данные частично (например у тебя на графике аномалии, ты знаешь, где они и хочешь детализировать данные, которые находятся рядом) - и память закончилась. Это несерьезно.

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

Ну конечно. А в python-shell тогда почему сделано нормально? :-)

Офлайн

#8 Июнь 3, 2018 07:06:06

xzvf
Зарегистрирован: 2016-09-04
Сообщения: 12
Репутация: +  0  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

Кстати, данный вопрос как оказалось волнует не меня одного: https://stackoverflow.com/questions/20814887/completely-disable-ipython-output-caching.

Офлайн

#9 Июнь 3, 2018 08:09:53

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9873
Репутация: +  853  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

xzvf
Есть вывод других более мелких данных, вывод которых накапливается и отжирает память.
Накапливается, отжирает
In [1]: list(range(5))
Out[1]: [0, 1, 2, 3, 4]

In [2]: list(range(5))
Out[2]: [0, 1, 2, 3, 4]

In [3]: list(range(5))
Out[3]: [0, 1, 2, 3, 4]

In [4]: list(range(5))
Out[4]: [0, 1, 2, 3, 4]

In [5]: list(range(5))
Out[5]: [0, 1, 2, 3, 4]

In [6]: list(range(5))
Out[6]: [0, 1, 2, 3, 4]

In [7]: list(range(5))
Out[7]: [0, 1, 2, 3, 4]

In [8]: list(range(5))
Out[8]: [0, 1, 2, 3, 4]

In [9]: list(range(5))
Out[9]: [0, 1, 2, 3, 4]

In [10]: list(range(5))
Out[10]: [0, 1, 2, 3, 4]

In [11]: list(range(5))
Out[11]: [0, 1, 2, 3, 4]

In [12]: list(range(5))
Out[12]: [0, 1, 2, 3, 4]

In [13]: list(range(5))
Out[13]: [0, 1, 2, 3, 4]

In [14]: list(range(5))
Out[14]: [0, 1, 2, 3, 4]

In [15]: list(range(5))
Out[15]: [0, 1, 2, 3, 4]

In [16]: _oh
Out[16]:
{1: [0, 1, 2, 3, 4],
2: [0, 1, 2, 3, 4],
3: [0, 1, 2, 3, 4],
4: [0, 1, 2, 3, 4],
5: [0, 1, 2, 3, 4],
6: [0, 1, 2, 3, 4],
7: [0, 1, 2, 3, 4],
8: [0, 1, 2, 3, 4],
9: [0, 1, 2, 3, 4],
10: [0, 1, 2, 3, 4],
11: [0, 1, 2, 3, 4],
12: [0, 1, 2, 3, 4],
13: [0, 1, 2, 3, 4],
14: [0, 1, 2, 3, 4],
15: [0, 1, 2, 3, 4]}

In [17]: id(_oh[1])
Out[17]: 140185325829128

In [18]: id(_oh[2])
Out[18]: 140185325829960

In [19]: id(_oh[15])
Out[19]: 140185323832840

In [20]:
Значит, не продуман кеш у них. В нормальных условиях нужно иметь возможность тонкой настройки.
Пока в словаре значения висят, память не будет освобождена.

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



Отредактировано py.user.next (Июнь 3, 2018 08:15:43)

Офлайн

#10 Июнь 3, 2018 10:11:47

xzvf
Зарегистрирован: 2016-09-04
Сообщения: 12
Репутация: +  0  -
Профиль   Отправить e-mail  

ipython memory leak и принцип выделения памяти в python repl

Ну, проблему я собственно решил, процентов на 90%. На этом, наверное, и остановлюсь. В любом случае, спасибо за помощь :-)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version