如何确定Python 2.7.5中国际化字符串的数量?

11
在早期版本的Python中(我不记得是哪个版本),调用gc.get_referrers来获取一个任意interned字符串的引用,可以用于获得interned字典的长度。
但是在Python 2.7.5中,这种方法已经不再有效: gc.get_referrers(...) 不再包含返回列表中的interned字典。
在Python 2.7.5中,是否有其他方法来确定interned字符串的数量?如果有,它是什么?

2
你为什么在意呢?你想通过这种低级别的版本特定的黑客手段实现什么目的?哦,是的,2.7.12是当前版本,那么为什么你需要关注一个已经超过3年的发布版本的细节呢?我不是要表现敌对,但我无法理解为什么这会有任何影响。 - cco
1
(a)我关心这个问题,因为我对了解Python进程的内存使用情况很感兴趣,这是一个额外的数据点。(b)我对Python 2.7.5很感兴趣,因为这是我们产品中使用的版本,尽管我怀疑在Python 2.7.12中得到的答案也会相同。 - jchl
谢谢你的回答。我从来没有花时间去调查“interned”字典的大小,因为我的应用程序中的(非文字)字符串一直是更重要的,所以确保我只有每个字符串的一个副本是我花时间的地方。因此,我仍然很好奇你的目标是什么——如果你有你所问的信息,你会如何使用它? - cco
确实,我在这里请求的特定数据(内部字符串的数量)可能本身并不那么有用,但它是一个起点。更有趣的是:内部字符串的总大小;interned 字典本身的大小;从其他地方没有引用的内部字符串的数量(和大小);仅从一个地方引用的内部字符串的数量(和大小)。这些一起有助于回答问题:我们是否通过不必要地将字符串放入池中而浪费了大量内存。 - jchl
1
文档(https://docs.python.org/2/library/functions.html#non-essential-built-in-funcs)指出,自2.3版本以来,内部字符串不再是不朽的,因此没有至少一个外部引用来保持其存活的内部字符串。 - cco
将字符串进行实习并不会延长它们的生命周期,因此您很难通过过度积极地进行实习而浪费大量空间。 - user2357112
2个回答

3
你可以实现这个功能,但所有选项都很混乱,充满了附加条件,几乎没有用处,因此,首先考虑一下你是否真的想这样做。
将字符串存入池中并不能延长其生命周期。你不必担心存储在池中的字典会无限增长,充满了你不需要的字符串。因此,字符串池化不太可能成为一个实际的内存问题,并且了解有多少字符串被池化可能是相当无用的。
如果你仍然想这样做,让我们来看看你的选择。
正确的方法可能是使用自己的字符串缓存实现... 但是 Python 的弱引用支持不够好,无法创建字符串的弱引用。这意味着如果您尝试此方法,则要么传递自己的可弱引用字符串包装器,要么永远保持缓存的字符串处于活动状态。这两个选项都很糟糕。

实际上有一个函数可以打印您所询问的信息...但它也会取消所有的内部引用。它的存在是一项实现细节,只能通过C API访问,因此我们需要使用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

总大小是字符串长度的总和,因此不包括对象头或空终止符。

你可能不太满意每次想要检查有多少个字符串被interned时都需要释放所有interned strings。不幸的是,Python甚至没有通过C API或GC hooks暴露interned dict。那么还有什么可尝试的呢?好吧,进一步尝试更疯狂的选择,有调试器。

ecatmur发布了一个疯狂的hack,启动一个未经处理的GDB进程,并使用条件断点来访问errnomap,这是一个非常类似于interned dict的字典,您希望访问的是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,进行编译和安装。您可能希望使用虚拟环境将其与普通的Python解释器分开。完成这个糟糕的 hack 后,您可以执行以下操作:

import ctypes

AwfulHackToGetTheInternedDict = ctypes.pythonapi.AwfulHackToGetTheInternedDict

AwfulHackToGetTheInternedDict.argtypes = ()
AwfulHackToGetTheInternedDict.restype = ctypes.py_object

interned = AwfulHackToGetTheInternedDict()

获取所有已内部化字符串的字典。


那么,这些是你的选择,或者至少是我想到的选择。我还尝试过强制GC跟踪一个字符串,然后将其合并以使interned dict通过GC可见,但在字符串上调用PyObject_GC_Track会导致致命错误,因此这种方法行不通。


非常感谢您提供如此全面的答案。 - jchl

0

针对您的需求,我认为真正的解决方案是使用更强大的内存分析工具。

有几个选项可供选择,例如在pypi上免费提供的memory_profiler


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接