Как определить количество интернированных строк в Python 2.7.5?

В более ранней версии Python (я не помню, какой) вызов gc.get_referrers для произвольной интернированной строки может быть использован для получения ссылки на interned dict, который затем может быть запрошен для его длины.

Но это больше не работает в Python 2.7.5: gc.get_referrers(...) больше не включает в себя interned dict в списке, который он возвращает.

Есть ли другой способ, в Python 2.7.5, определить количество интернированных строк? Если да, то как?

Ответ 1

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

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

Если вы все еще хотите это сделать, отпустите свои варианты.


Правильный путь, вероятно, должен был бы использовать вашу собственную интернирующую реализацию... за исключением того, что слабая поддержка ссылок Python не позволяет создавать слабые ссылки на строки. Это означает, что если вы попробуете этот подход, вы застряли либо в обходе своих слабых ссылочных оберток строк, либо сохраняете интернированные строки живыми навсегда. Оба варианта ужасны.


На самом деле есть функция, которая печатает информацию, о которой вы просите... но она также деинтерминирует все. Его существование является детальностью реализации, и оно доступно только через API C, поэтому нам нужно использовать ctypes.pythonapi, чтобы получить его.

import ctypes

_Py_ReleaseInternedStrings = ctypes.pythonapi._Py_ReleaseInternedStrings

_Py_ReleaseInternedStrings.argtypes = ()
_Py_ReleaseInternedStrings.restype = None

_Py_ReleaseInternedStrings()

Вывод:

releasing 3461 interned strings
total size of all interned strings: 33685/0 mortal/immortal

Суммарными размерами являются суммы строк, поэтому они не включают заголовки объектов или нулевые терминаторы.


Вам, вероятно, не нравится, что нужно выпускать все интернированные строки каждый раз, когда вы хотите проверить, сколько их было. К сожалению, Python не раскрывает интернированного dict, даже через C API или через GC hooks. Что еще вы могли попробовать? Ну, перейдя к еще более сумасшедшим вариантам, там отладчик.

ecatmur отправил сумасшедший взлом, запустив процесс GDB в автоматическом режиме и используя условную точку останова, чтобы добраться до errnomap, очень похоже на interned dict, к которому вы хотите получить доступ. Это может быть адаптировано для доступа к interned dict вместо этого. Это было бы очень не переносным и чрезвычайно сложно поддерживать.


Запуск отладчика также является ужасным вариантом. Что еще вы могли попробовать? Ну, вы всегда можете создать свою собственную сборку Python. Загрузите источник из python.org, добавьте

PyObject *
AwfulHackToGetTheInternedDict(void)
{
    if (interned == NULL) {
        // No interned dict yet.
        Py_RETURN_NONE;
    }
    Py_INCREF(interned);
    return interned;
}

до Objects/stringobject.c, сборки и установки. Вероятно, вы захотите использовать virtualenv, чтобы сохранить это отдельно от вашего обычного интерпретатора Python. С помощью этого ужасного взлома вы можете сделать

import ctypes

AwfulHackToGetTheInternedDict = ctypes.pythonapi.AwfulHackToGetTheInternedDict

AwfulHackToGetTheInternedDict.argtypes = ()
AwfulHackToGetTheInternedDict = ctypes.py_object

interned = AwfulHackToGetTheInternedDict()

чтобы получить dict всех интернированных строк.


Итак, это ваши варианты или, по крайней мере, варианты, о которых я думал. Я также попытался заставить GC отслеживать строку, а затем интернировать ее, чтобы сделать интернированный dict видимым через GC, но вызов PyObject_GC_Track в строке вызвал фатальную ошибку, так что это не сработает.

Ответ 2

В ваших целях я считаю, что реальный ответ заключается в использовании более надежного решения для профилирования памяти.

Для этого есть несколько вариантов, таких как опция memory_profiler на pypi.