Найти - Пользователи
Полная версия: ipython memory leak и принцип выделения памяти в python repl
Начало » Python для экспертов » ipython memory leak и принцип выделения памяти в python repl
1
xzvf
Всем привет. Столкнулся с таким поведением интерактивной оболочки 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
Пока что нагуглились только вариант удалять кеш (_, _1, _3, … _N) вручную с помощью %xdel или полностью очищать неймспейс текущей конфигурации с помощью команды get_ipython().reset(), которая собирает (collect) все пользовательские переменные. Но это все не то.
py.user.next
? (справка)
* 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.

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

Без присваивания удобно, чтобы посмотреть сразу результат, а не использовать потом еще 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
Удалось добиться поведения стандартного python-shell заменой self.__ и self.___ на None в исходниках IPython/core/displayhook.py. Наверное, если еще немного повозиться, можно организовать и очистку памяти от последнего выражения (_), но пока и так уже лучше, чем было.
py.user.next
xzvf
random.random - приведен для примера, это может быть и ф-ция, которая применяется к такому большому датасету и возвращает 1 или 0.
Кешируется только вывод. Какая функция тормозит, если вывода этих данных нет?

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

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

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

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

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

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

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

Я в курсе.

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

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

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

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

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

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

xzvf
Кстати, данный вопрос как оказалось волнует не меня одного: https://stackoverflow.com/questions/20814887/completely-disable-ipython-output-caching.
py.user.next
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. А память будет резервироваться как для изначального набора данных.
Ну там вообще по-тупому сделано. Кеш, удерживающий каждый выведенный список, даже непонятно, зачем нужен. Но с другой стороны там можно редактировать прошлый ввод прямо с переводами строк (чего нет в стандартном репле), что ускоряет работу при демонстрациях и разных проверках кода.
xzvf
Ну, проблему я собственно решил, процентов на 90%. На этом, наверное, и остановлюсь. В любом случае, спасибо за помощь :-)
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB