• Технический директор Qiwi намайнил 500 000 биткоинов, то есть 2,4% теоретически возможного количества
    0
    Уже пришло уточнение: «не техдиректор, а разработчик»:
    Видимо дальше как в анекдоте: «не 500к биткоинов, а 500к рублей. и не российских, а белорусских. и не намайнил, а в тубмочке нашёл»
  • Использование памяти в Python
    0
    В примере 2a в начале модуля была строка:

    libc = ctypes.CDLL('libc.so.6')
    

  • Использование памяти в Python
    0
    Этот комментарий не верен. Intobject.c получает память напрямую через malloc и также ее освобождает. См. новый комментарий выше.
  • Использование памяти в Python
    +4
    Похоже, я был зомбирован статьями и комментариями в интернете, которые уже успели устареть. Почитал исходный код и кое-чего уяснил. Я попробую проиллюстрировать свои открытия на примерах, но сначала немного теории…

    Объекты 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, буду очень признателен.
  • Использование памяти в Python
    0
    Я так понимаю дело в реализации PyMalloc, а именно в том что он не хочет отдавать память, которая использовалась для хранения int/float обратно операционной системе после фактического уничтожения объектов, либо использовать ее для других целей, кроме как для хранения новых int/float.

    То есть, если мы создадим много объектов (a = range(10**6)), то мы забираем 11.4mb у ОС на хранение int-ов и примерно 4mb на хранение списка. Если после этого удалим «a» (del a), то ОС вернется обратно 4mb, который высвободится из-под списка, но 11.4mb останется в распоряжение питона. Эту память может будет использовать только под новые int-ы/float.
  • Использование памяти в Python
    0
    Смотрим официальную документацию (GC):

    gc.collect([generation])

    Изменения в версии 2.6. Free list-ы для некоторых встроенных типов очищаются, когда выполняется полная очистка или очистка с максимальной генерацией (2). Ввиду особенности реализации, некоторых объекты во free list-ах могут не удалятся, в частности, int и float.

    Насколько я понимаю, в нашем случае это верно. Никакого видимого эффекта после вызова gc.collect(2) не произошло.
  • Использование памяти в Python
    0
    Спасибо, я, конечно, думал про целые числа (как противоположность дробным), а про «простые».