CPython 3.11中sys.getrefcount()返回超大引用计数原因咨询
你观察到的现象确实和PEP 683 - 不朽对象(Immortal Objects):使用固定引用计数直接相关,下面具体解释:
现象回顾
在CPython 3.11中,-5到256区间的预缓存整数调用sys.getrefcount()会返回千万级别的数值,而非预缓存的整数(如-6、257)则保持正常计数:
Python 3.11.2 (tags/v3.11.2:878ead1, Feb 7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> for i in (-6, -5, 0, 255, 256, 257): ... print(i, sys.getrefcount(i)) ... -6 5 -5 1000000004 0 1000000535 255 1000000010 256 1000000040 257 5
而CPython 3.10中这些预缓存整数的引用计数是正常的实际引用统计:
Python 3.10.8 (tags/v3.10.8:aaaf517, Oct 11 2022, 16:50:30) [MSC v.1933 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> for i in (-6, -5, 0, 255, 256, 257): ... print(i, sys.getrefcount(i)) ... -6 5 -5 6 0 234 255 8 256 26 257 5
背后的原因
PEP 683的核心目标是优化GC性能,将一些全局不可变对象(包括-5到256的预缓存整数、None、True、False、部分字符串常量等)标记为"不朽对象"。
为了实现这一点,CPython给这类对象设置了一个固定的超大引用计数值(具体是2^30左右,也就是你看到的1000000000级别的数值)。这个数值的作用是让GC永远不会尝试回收它们——因为引用计数永远不会降到0,GC会直接跳过对这类对象的引用计数检查,从而减少GC的工作量。
在CPython 3.10及更早版本中,这些预缓存对象的引用计数是正常统计实际引用数量的,所以返回值是合理的小数值。
为什么文档没提及这个变化
这个现象属于PEP 683实现的附带行为,而非sys.getrefcount()本身的功能变更——sys.getrefcount()只是忠实地返回对象当前的引用计数值,而不朽对象的引用计数被人为设置成了超大值。官方文档可能因为这不是API的直接变更,所以没有单独在sys.getrefcount()或Python 3.11新特性文档中提及。
需要注意的是,这个超大引用计数对实际代码没有负面影响:这些对象本来就是全局缓存、不会被回收的,引用计数的具体数值在这里仅作为"不朽对象"的标识,不代表实际的引用数量。
内容的提问来源于stack exchange,提问作者Mark Tolonen

