Комментарии 30
Эцсамое. Int — не «простое число». Int — знаковое целое.
Немного на ту же тему.
Как-то надо было по быстрому обработать ~11млн простых структур (полями были целочисленные и короткие строковые поля). Хотел на python, но ему не хватило 8Г оперативки :( В итоге было переписано на C.
Вот тогда я почувствовал, насколько python прожорлив.
Как-то надо было по быстрому обработать ~11млн простых структур (полями были целочисленные и короткие строковые поля). Хотел на python, но ему не хватило 8Г оперативки :( В итоге было переписано на C.
Вот тогда я почувствовал, насколько python прожорлив.
Numpy Вам в помощь.
Да задача решена еще тогда была (на C памяти заняло около 2Г). Не ожидал на тот момент, что python окажется настолько прожорливым.
Ну может всетакие надо было заюзать итераторы? А то 11 милион структур в памяти держать — имхо не лучшее решение. Напоминает решения из серии — я не могу собрать кубик Рубика, я его молоточком а потом суперклеем :)
Давайте не будем обсуждать дальше решение в топике не про него) Можно в личке, если хочется.
Простите, но… вы хотели запихнуть 11млн простых структур в память, чтобы их обработать?
Да, был файл, содержащий данные с неким подобием RTL сжатия. ~11млн это уже после обработки одинаковых значений (что-то вида 500 одинаковых значений подряд в столбце или 3000 одинаковых подряд в строке).
А точно нужно было в памяти всё сразу держать? Часто можно код организовывать так, чтоб это не требовалось. В Python для этого всякие плюшки есть — генераторы, модуль itertools. Значительно удобнее, чем на C :) Занимало бы тогда мегабайт 50 вместо 2 гигабайт.
Потому и был взят python, чтобы быстро и просто сбацать обработку :) Данные хранились поколоночно, но при этом могла быть отметка «эту ячейку повторить в строке N раз». А результат обработки нужно было сохранять построчно в другом формате. И 2Г — это на C, питон выедал 7.5Г и убивался OOM killer'ом.
Не держать все одновременно может бы и вышло, но логика получилась бы совсем не тривиальная. А ведь кроме считывания там основной объем занимала обработка :)
В общем, на C это легло хорошо и быстро.
Не держать все одновременно может бы и вышло, но логика получилась бы совсем не тривиальная. А ведь кроме считывания там основной объем занимала обработка :)
В общем, на C это легло хорошо и быстро.
С целыми числами не все так просто, кстати :) В CPython есть так называемые «free lists» — куски памяти, выделяемые под объекты заранее, чтоб не вызывать malloc по многу раз. Есть отдельные free lists для различных встроенных типов (словарей, кортежей, чисел и т.д.)
Так вот, если для словарей и кортежей размер free list ограничен (1000 что-ли объектов), то в Python 2.x размер free list для чисел не ограничен. Это означает, что если в памяти в какой-то момент одновременно находится миллион разных целых чисел (экземпляров int), то они останутся там навсегда (до завершения процесса). Сборщик мусора этим всем не занимается, т.к. к сборке мусора это все отношения не имеет. Пробежались по xrange(1000000) — все ок, пробежались по range(1000000) — миллион объектов int остался в памяти навсегда.
В одной из 3.х версий это как-то обошли.
Ну и, понятное дело, если важно, сколько миллион чисел занимает, то его лучше хранить в array.array (или numpy.ndarray), чтоб не создавать ненужные объекты int и убрать оверхед на все эти указатели.
Так вот, если для словарей и кортежей размер free list ограничен (1000 что-ли объектов), то в Python 2.x размер free list для чисел не ограничен. Это означает, что если в памяти в какой-то момент одновременно находится миллион разных целых чисел (экземпляров int), то они останутся там навсегда (до завершения процесса). Сборщик мусора этим всем не занимается, т.к. к сборке мусора это все отношения не имеет. Пробежались по xrange(1000000) — все ок, пробежались по range(1000000) — миллион объектов int остался в памяти навсегда.
В одной из 3.х версий это как-то обошли.
Ну и, понятное дело, если важно, сколько миллион чисел занимает, то его лучше хранить в array.array (или numpy.ndarray), чтоб не создавать ненужные объекты int и убрать оверхед на все эти указатели.
Звучит как баг и не очень понятно, при чём тут free list. Пример же на сравнении xrange() и range() сравнивает функцию-генератор и функцию, генерирующую список.
Тут или Вы чего-то не поняли, или это реально баг в Python 2.x.
Можно пример кода, который подтверждал бы это? Висение миллиона интов легко видно по потреблению процессом памяти.
Тут или Вы чего-то не поняли, или это реально баг в Python 2.x.
Можно пример кода, который подтверждал бы это? Висение миллиона интов легко видно по потреблению процессом памяти.
Легко :)
Если вызвать функцию create_temp_strings(), то она в пике займет мегабайт 700 памяти, но после завершения потребление памяти вернется к прежнему уровню. Если вызвать create_temp_ints(), то под Python 2.7 она займет в пике мегабайт 250, и они так и останутся занятыми после ее завершения. И там, и там используется xrange; разница в том, что в create_temp_strings() инты не находятся в памяти одновременно, а перебираются по одному. То, что все строки находятся в памяти одновременно, к проблеме не ведет, т.к. для строк объем преаллокации ограничен.
См., например, примечания к docs.python.org/2/library/gc.html#gc.collect и deeplearning.net/software/theano/tutorial/python-memory-management.html#internal-memory-management
Я, может, неправильно это все «free lists» называю — там для чисел вроде списки пулов, а не просто списки. Но это уже детали.
# примеры для Python 2.7
def create_temp_strings():
temp_value = [str(x) for x in xrange(10000000)]
def create_temp_ints():
temp_value = [x*2 for x in xrange(10000000)]
Если вызвать функцию create_temp_strings(), то она в пике займет мегабайт 700 памяти, но после завершения потребление памяти вернется к прежнему уровню. Если вызвать create_temp_ints(), то под Python 2.7 она займет в пике мегабайт 250, и они так и останутся занятыми после ее завершения. И там, и там используется xrange; разница в том, что в create_temp_strings() инты не находятся в памяти одновременно, а перебираются по одному. То, что все строки находятся в памяти одновременно, к проблеме не ведет, т.к. для строк объем преаллокации ограничен.
См., например, примечания к docs.python.org/2/library/gc.html#gc.collect и deeplearning.net/software/theano/tutorial/python-memory-management.html#internal-memory-management
Я, может, неправильно это все «free lists» называю — там для чисел вроде списки пулов, а не просто списки. Но это уже детали.
bugs.python.org/issue1338264 — вот issue 2005 года про это, для Python 2.4, закрыт как wont fix :) Цитата:
Space for integer objects in particular lives in an immortal free list of unbounded size… If you don't want that, don't do that ;-)
Спасибо за примеры, надо будет порыть на досуге… :)
Есть у меня на примете питоновые штуки, которые едят память как не в себя, пробую их покусать с разных сторон. Могу попробовать оформить «покусанное» в виде мыслей/вопросов о том, как исследовать потребления памяти, кстати, если интересно. Правда, выводов и готовых рецептов пока нет. :(
Есть у меня на примете питоновые штуки, которые едят память как не в себя, пробую их покусать с разных сторон. Могу попробовать оформить «покусанное» в виде мыслей/вопросов о том, как исследовать потребления памяти, кстати, если интересно. Правда, выводов и готовых рецептов пока нет. :(
Попробовал, что-то в этом действительно есть.
Правда, для 2.6+ похоже, что часть статей устарела (в некотором смысле), и эти free list'ы можно чистить.
Их чистит gc.collect() при вызове с максимальной генерацией (обычно это gc.collect(2)), проверял на Вашем примере с функциями.
Правда, как часто вызывается такая сборка мусора (и вызывается ли вообще) — я не знаю.
Правда, для 2.6+ похоже, что часть статей устарела (в некотором смысле), и эти free list'ы можно чистить.
Их чистит gc.collect() при вызове с максимальной генерацией (обычно это gc.collect(2)), проверял на Вашем примере с функциями.
Правда, как часто вызывается такая сборка мусора (и вызывается ли вообще) — я не знаю.
Смотрим официальную документацию (GC):
gc.collect([generation])
…
Изменения в версии 2.6. Free list-ы для некоторых встроенных типов очищаются, когда выполняется полная очистка или очистка с максимальной генерацией (2). Ввиду особенности реализации, некоторых объекты во free list-ах могут не удалятся, в частности, int и float.
Насколько я понимаю, в нашем случае это верно. Никакого видимого эффекта после вызова gc.collect(2) не произошло.
gc.collect([generation])
…
Изменения в версии 2.6. Free list-ы для некоторых встроенных типов очищаются, когда выполняется полная очистка или очистка с максимальной генерацией (2). Ввиду особенности реализации, некоторых объекты во free list-ах могут не удалятся, в частности, int и float.
Насколько я понимаю, в нашем случае это верно. Никакого видимого эффекта после вызова gc.collect(2) не произошло.
Странно, я читал исходники 2.7.5, и там список int'ов вполне себе чистится (в том случае, если эти int'ы не используются, т.е. их refcount = 0).
И вызов gc.collect(2) у меня приводил к тому, что память освобождалась (для вышеприведённого примера с функцией), проверял на Win ActiveState Python 2.7.2 x86_64.
Может, у Вас версия другая какая?..
И вызов gc.collect(2) у меня приводил к тому, что память освобождалась (для вышеприведённого примера с функцией), проверял на Win ActiveState Python 2.7.2 x86_64.
Может, у Вас версия другая какая?..
Посмотрел сейчас внимательно в код (Objects/intobject.c, функция PyInt_ClearFreeList), часть int'ов может быть не очищена при очистке free list'a, поскольку последний хранится в виде связного списка небольших массивов, и каждый массив удаляется только в том случае, если в нём остались только int'ы с нулевым количеством ссылок (т.е. один «живой» int будет держать весь массив).
Похоже, я был зомбирован статьями и комментариями в интернете, которые уже успели устареть. Почитал исходный код и кое-чего уяснил. Я попробую проиллюстрировать свои открытия на примерах, но сначала немного теории…
Объекты Int (как и некоторые другие встроенные типы) поддерживают собственный аллокатор памяти. При необходимости память запрашивается вызовом malloc (в коде используется макрос PyMem_MALLOC, но это просто обертка вокруг malloc; не путать с PyMem_Malloc) блоками по ~1kb.
Есть два связанных списка: block_list — список всех выделенных блоков памяти и free_list — список всех свободных ячеек в этих блоках. Когда создается новый Int, то первым делом (еще есть кэширование значений от -5 до 256, но не будем об этом...) проверяется указатель free_list. Если он не равен null, то Int записывается в свободную ячейку, а free_list укорачивается на один элемент. Если null, то значит, свободных ячеек больше нет. В этом случае вызывается функция fill_free_list, которая запрашивает у malloc новый блок и добавляет его ячейки в список free_list.
Когда количество ссылок на Int становится равно нулю, его адрес добавляется в конец списка free_list.
Посмотреть картинки и подробнее почитать обо всем этом можно здесь: www.laurentluce.com/posts/python-integer-objects-implementation/
Также есть функция PyInt_ClearFreeList, которая отыскивает среди block_list-ов такие, которые полностью состоят из свободных ячеек, вырезает их из списка и вызывает на них malloc_free (опять через обертку PyMem_FREE). Эта функция (в первоначальном название PyInt_CompactFreeList) была добавлена в версии python 2.6 alpha 1 (см. bugs.python.org/issue1953). Следующие комментарий в начале модуля intobject.c явным образом отрицает ее существование, потому что он был написан на 6 лет раньше и с тех пор не обновлялся. Не верьте ему :).
Сама функция PyInt_ClearFreeList регулярно дергается сборщиком мусора. Вот так выглядит ее вызов в версии 2.7.5:
Итак, обещанные примеры.
Для наблюдения за расходом памяти я использовал memory_profiler в связке с psutil. Чтобы освободить память я напрямую дергаю PyInt_ClearFreeList (через ctypes.pythonapi) и, на всякий случай, gc.collect(2), чтобы доказать, что ничего нового не произойдет.
Мы забрали память у ОС, а потом его вернули. Вот бы так было всегда…
Мы создали кучу Int-ов, затем создали еще один, а потом нашу кучу удалили. В итоге память в ОС не вернулось. Только после того как мы удалили последний Int (и, соответственно, освободился block_list, который он «держал») память наконец-то вернулась на место. Здесь вместо int('300') могли бы быть любые расчеты и прочие операции, которые создают Int-ов.
Можно ли как-то избежать освобождения последнего block_list-а?
Здесь мы вызываем функцию malloc_trim из libc и все становиться на место. В примере это сработало, но я бы не стал использовать такой трюк в реальном проекте.
Еще пару примеров можно почитать здесь:
nuald.blogspot.ru/2013/06/memory-reclaiming-in-python.html
bugs.python.org/msg134008
Здесь нам не помогло ни тщательное удаление всех Int-ов, ни ClearFreeList, ни malloc_trim, ни gc.collect(2). Память осталась в распоряжение malloc-а и в ОС не вернулась.
PS. Примеры запускал под Python 2.7.3 Linux 2.6.32-33 i686. Если Вы можете проверить их под Windows, буду очень признателен.
Объекты Int (как и некоторые другие встроенные типы) поддерживают собственный аллокатор памяти. При необходимости память запрашивается вызовом malloc (в коде используется макрос PyMem_MALLOC, но это просто обертка вокруг malloc; не путать с PyMem_Malloc) блоками по ~1kb.
Есть два связанных списка: block_list — список всех выделенных блоков памяти и free_list — список всех свободных ячеек в этих блоках. Когда создается новый Int, то первым делом (еще есть кэширование значений от -5 до 256, но не будем об этом...) проверяется указатель free_list. Если он не равен null, то Int записывается в свободную ячейку, а free_list укорачивается на один элемент. Если null, то значит, свободных ячеек больше нет. В этом случае вызывается функция fill_free_list, которая запрашивает у malloc новый блок и добавляет его ячейки в список free_list.
Когда количество ссылок на Int становится равно нулю, его адрес добавляется в конец списка free_list.
Посмотреть картинки и подробнее почитать обо всем этом можно здесь: www.laurentluce.com/posts/python-integer-objects-implementation/
Также есть функция PyInt_ClearFreeList, которая отыскивает среди block_list-ов такие, которые полностью состоят из свободных ячеек, вырезает их из списка и вызывает на них malloc_free (опять через обертку PyMem_FREE). Эта функция (в первоначальном название PyInt_CompactFreeList) была добавлена в версии python 2.6 alpha 1 (см. bugs.python.org/issue1953). Следующие комментарий в начале модуля intobject.c явным образом отрицает ее существование, потому что он был написан на 6 лет раньше и с тех пор не обновлялся. Не верьте ему :).
hg blame intobject.c
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: /* Integers are quite normal objects, to make object handling uniform.
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: (Using odd pointers to represent integers would save much space
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: but require extra checks for this special case throughout the code.)
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000: Since a typical Python program spends much of its time allocating
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: and deallocating integers, these operations should be very fast.
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: Therefore we use a dedicated allocation scheme with a much lower
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: overhead (in space and time) than straight malloc(): a simple
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: dedicated free list, filled when necessary with memory from malloc().
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000:
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000: block_list is a singly-linked list of all PyIntBlocks ever allocated,
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000: linked via their next members. PyIntBlocks are never returned to the
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000: system before shutdown (PyInt_Fini).
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000:
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000: free_list is a singly-linked list of available PyIntObjects, linked
tim 01478a132908 Sun Apr 28 16:57:34 2002 +0000: via abuse of their ob_type members.
guido a6934380c6e7 Thu Dec 20 15:06:42 1990 +0000: */
Сама функция PyInt_ClearFreeList регулярно дергается сборщиком мусора. Вот так выглядит ее вызов в версии 2.7.5:
Modules/gcmodule.c
/* This is the main function. Read this to understand how the
* collection process works. */
static Py_ssize_t
collect(int generation)
{
…
здесь много кода
...
/* Clear free list only during the collection of the highest
* generation */
if (generation == NUM_GENERATIONS-1) {
clear_freelists();
}
/* Clear all free lists
* All free lists are cleared during the collection of the highest generation.
* Allocated items in the free list may keep a pymalloc arena occupied.
* Clearing the free lists may give back memory to the OS earlier.
*/
static void
clear_freelists(void)
{
(void)PyMethod_ClearFreeList();
(void)PyFrame_ClearFreeList();
(void)PyCFunction_ClearFreeList();
(void)PyTuple_ClearFreeList();
#ifdef Py_USING_UNICODE
(void)PyUnicode_ClearFreeList();
#endif
(void)PyInt_ClearFreeList();
(void)PyFloat_ClearFreeList();
}
Итак, обещанные примеры.
Для наблюдения за расходом памяти я использовал memory_profiler в связке с psutil. Чтобы освободить память я напрямую дергаю PyInt_ClearFreeList (через ctypes.pythonapi) и, на всякий случай, gc.collect(2), чтобы доказать, что ничего нового не произойдет.
Пример 1. Здесь все хорошо.
Filename: tests/test_good.py
Line # Mem usage Increment Line Contents
================================================
6 @profile
7 5.785 MB 0.000 MB def func():
8 21.117 MB 15.332 MB a = range(10**6)
9 17.301 MB -3.816 MB del a
10 5.895 MB -11.406 MB ctypes.pythonapi.PyInt_ClearFreeList()
11 5.895 MB 0.000 MB gc.collect(2)
Мы забрали память у ОС, а потом его вернули. Вот бы так было всегда…
Пример 2. Что-то настораживает...
Filename: tests/test_bad.py
Line # Mem usage Increment Line Contents
================================================
6 @profile
7 5.781 MB 0.000 MB def func():
8 21.117 MB 15.336 MB a = range(10**6)
9 21.117 MB 0.000 MB b = int('300')
10 17.301 MB -3.816 MB del a
11 17.309 MB 0.008 MB ctypes.pythonapi.PyInt_ClearFreeList()
12 17.309 MB 0.000 MB del b
13 5.895 MB -11.414 MB ctypes.pythonapi.PyInt_ClearFreeList()
14 5.895 MB 0.000 MB gc.collect(2)
Мы создали кучу Int-ов, затем создали еще один, а потом нашу кучу удалили. В итоге память в ОС не вернулось. Только после того как мы удалили последний Int (и, соответственно, освободился block_list, который он «держал») память наконец-то вернулась на место. Здесь вместо int('300') могли бы быть любые расчеты и прочие операции, которые создают Int-ов.
Можно ли как-то избежать освобождения последнего block_list-а?
Пример 2а. Грязный хак.
Filename: tests/test_bad_hack.py
Line # Mem usage Increment Line Contents
================================================
6 @profile
7 5.789 MB 0.000 MB def func():
8 21.117 MB 15.328 MB a = range(10**6)
9 21.117 MB 0.000 MB b = int('300')
10 17.301 MB -3.816 MB del a
11 17.309 MB 0.008 MB ctypes.pythonapi.PyInt_ClearFreeList()
12 5.785 MB -11.523 MB libc.malloc_trim(0)
13 #del b
14 5.785 MB 0.000 MB ctypes.pythonapi.PyInt_ClearFreeList()
15 5.785 MB 0.000 MB gc.collect(2)
Здесь мы вызываем функцию malloc_trim из libc и все становиться на место. В примере это сработало, но я бы не стал использовать такой трюк в реальном проекте.
Еще пару примеров можно почитать здесь:
nuald.blogspot.ru/2013/06/memory-reclaiming-in-python.html
bugs.python.org/msg134008
Пример 3. Грустный..
Filename: tests/test_ugly.py
Line # Mem usage Increment Line Contents
================================================
6 @profile
7 5.781 MB 0.000 MB def func():
8 5.781 MB 0.000 MB i = 0
9 5.781 MB 0.000 MB a = []
10 13.094 MB 7.312 MB while i < 10**5: # 10**5 чтобы было быстрее
11 13.094 MB 0.000 MB a.append(i)
12 13.094 MB 0.000 MB i += 1
13 12.711 MB -0.383 MB del a
14 12.711 MB 0.000 MB del i
15 12.719 MB 0.008 MB ctypes.pythonapi.PyInt_ClearFreeList()
16 12.711 MB -0.008 MB libc.malloc_trim(0)
17 12.711 MB 0.000 MB ctypes.pythonapi.PyInt_ClearFreeList()
18 12.711 MB 0.000 MB gc.collect(2)
Здесь нам не помогло ни тщательное удаление всех Int-ов, ни ClearFreeList, ни malloc_trim, ни gc.collect(2). Память осталась в распоряжение malloc-а и в ОС не вернулась.
PS. Примеры запускал под Python 2.7.3 Linux 2.6.32-33 i686. Если Вы можете проверить их под Windows, буду очень признателен.
В примере 2a в начале модуля была строка:
libc = ctypes.CDLL('libc.so.6')
Хм, не очень понятно, почему test_bad.py и test_ugly.py так себя ведут, вроде бы в реализации intobject'a есть освобождение промежуточных блоков, если их никто не держит.
Есть у меня подозрение, что это наведённый эффект от библиотеки memory_profile :) Доказать сейчас не возьмусь, но идея для проверки примерно такая — берём Ваш test_ugly.py, выкидываем декоратор, в каждой строчке втыкаем что-нибудь вроде time.sleep(0.1) (не втыкаем только в цикл).
Потребление памяти меряем внешним процессом.
Есть у меня подозрение, что это наведённый эффект от библиотеки memory_profile :) Доказать сейчас не возьмусь, но идея для проверки примерно такая — берём Ваш test_ugly.py, выкидываем декоратор, в каждой строчке втыкаем что-нибудь вроде time.sleep(0.1) (не втыкаем только в цикл).
Потребление памяти меряем внешним процессом.
Попробовал реализовать свою идею.
test_ugly.py:
Запускалка-измерялка b.py:
Построил график потребления виртуальной памяти (ActivePython 2.7.2 x86_64, Windows 7 x86_64):
По горизонтали — время, по вертикали — потребление памяти в мегабайтах.
Первый скачок — очевидно, инициализация самого интерпретатора. Второй — создание архива. Скачок вниз — освобождение. Освобождается, кстати, существенно больше, чем в Вашем примере, но явно не вся память.
test_ugly.py:
import ctypes
import gc
import time
PAUSE = 1.0
def func():
time.sleep(PAUSE)
i = 0
time.sleep(PAUSE)
a = []
time.sleep(PAUSE)
while i < 10**5:
a.append(i)
i += 1
time.sleep(PAUSE)
del a
time.sleep(PAUSE)
del i
time.sleep(PAUSE)
ctypes.pythonapi.PyInt_ClearFreeList()
time.sleep(PAUSE)
gc.collect(2)
time.sleep(PAUSE)
if __name__ == '__main__':
func()
Запускалка-измерялка b.py:
import psutil
import sys
import time
def main():
proc = psutil.Popen([sys.executable, 'test_ugly.py'])
stats = []
while proc.poll() is None:
# target is alive
stats.append((time.time(), proc.get_memory_info().vms / 1024.0 / 1024))
time.sleep(0.1)
with open('result.txt', 'w') as out:
out.write('%s\n' % '\n'.join('%s %.2f' % stat for stat in stats))
if __name__ == '__main__':
main()
Построил график потребления виртуальной памяти (ActivePython 2.7.2 x86_64, Windows 7 x86_64):
По горизонтали — время, по вертикали — потребление памяти в мегабайтах.
Первый скачок — очевидно, инициализация самого интерпретатора. Второй — создание архива. Скачок вниз — освобождение. Освобождается, кстати, существенно больше, чем в Вашем примере, но явно не вся память.
Да, кстати, размер такого массива из 105 int'ов в случае моего питона занимает примерно 3 мегабайта (в смысле сумма размера list'a и размера всех int'ов), так что вроде как освобождается почти всё. Наверно, только сам объект a уходит в free list list'ов…
З.Ы. В сообщении выше вместо «второй — создание архива» читать «второй — создание массива» :)
З.Ы. В сообщении выше вместо «второй — создание архива» читать «второй — создание массива» :)
Я так понимаю дело в реализации PyMalloc, а именно в том что он не хочет отдавать память, которая использовалась для хранения int/float обратно операционной системе после фактического уничтожения объектов, либо использовать ее для других целей, кроме как для хранения новых int/float.
То есть, если мы создадим много объектов (a = range(10**6)), то мы забираем 11.4mb у ОС на хранение int-ов и примерно 4mb на хранение списка. Если после этого удалим «a» (del a), то ОС вернется обратно 4mb, который высвободится из-под списка, но 11.4mb останется в распоряжение питона. Эту память может будет использовать только под новые int-ы/float.
То есть, если мы создадим много объектов (a = range(10**6)), то мы забираем 11.4mb у ОС на хранение int-ов и примерно 4mb на хранение списка. Если после этого удалим «a» (del a), то ОС вернется обратно 4mb, который высвободится из-под списка, но 11.4mb останется в распоряжение питона. Эту память может будет использовать только под новые int-ы/float.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Использование памяти в Python