id(1) 和 id(2) 返回的内存地址为什么相差 32?

2018-06-17 21:47:23 +08:00
 mimzy

测试环境 Python 3.6.4,现象:

>>> id(1)
4489990816

>>> id(2)
4489990848

我的想法:相差的 32 应该是 32 bits(存疑)。id() 在 CPython 中返回的是 1 和 2 在内存中的地址,由于 Python 每个对象都有标准对象头,类型指针和引用计数等信息,所以不能简单地用 -5 ~ 255 这个范围内的整数所占的最小空间去存储,4 bytes 的空间内(感觉有点小?)存储了一些其他信息。因为没有去了解 CPython 具体的实现,不知道自己想的对不对。然后就又有个一个问题:

>>> import sys

>>> sys.getsizeof(1)
28

文档中说 sys.getsizeof() "Return the size of an object in bytes." 并且 "Only the memory consumption directly attributed to the object is accounted for, not the memory consumption of objects it refers to." 请问这句话中的 directly attributed to 和 refers to 应该怎么理解?(听起来像英语阅读理解…_(:3 」∠)_)这个 size 真的是 bytes 么?

2972 次点击
所在节点    Python
14 条回复
wwqgtxx
2018-06-17 21:58:34 +08:00
实际上 Python 中的 int 并不是定长的。。。
>>> import sys
>>> sys.getsizeof(1)
28
>>> sys.getsizeof(100000000000000000000000)
36
>>> sys.getsizeof(1000000000000000000000000000000000000000000000000)
48
>>> sys.getsizeof(1000000000000000000000000000000000000000000000000000000000000000)
52
mimzy
2018-06-17 22:12:43 +08:00
@wwqgtxx #1 确实 貌似只要内存允许 可以无限变大…

>>> sys.getsizeof(2**999999)
133360

。。。
geelaw
2018-06-17 22:42:05 +08:00
你不应该假设连续制造的两个对象的地址也是“接近”的。

后面 Only ... to 这句话是想告诉你如下事实:考虑 A 对象具有 B 字段,B 字段的值是(指向) C 对象(的引用),A 对象的 size 不非要大于 C 对象的 size —— C 对象的 size 不会算入 A 对象的 size。类比到 C 的话

typedef struct { void *member1, *member2, *member3, *member4; } c_t;
typedef struct { c_t *b; } a_t;

则 sizeof(a_t) 是 sizeof(void *) 而不是 sizeof(void *) + sizeof(c_t)。
future0906
2018-06-17 22:56:31 +08:00
CPython 里面所有的小整数都是被预初始化,做成一个池的。
jmc891205
2018-06-17 23:06:29 +08:00
id(1)和 id(2)相差的是 32bytes 不是 32bits
在 CPython 的实现里小整数缓存是这样定义的:
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
在我的 64 位机器上 sizeof(PyLongObject)是 32bytes 因此 id(1)和 id(2)相差了 32bytes

在 CPython 的实现里也可以找到 PyLongObject 的定义:
typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */
struct _longobject {
----PyObject_VAR_HEAD
----digit ob_digit[1];
};
在我的机器上 PyObject_VAR_HEAD 占 24bytes, digit ob_digit[1]占 4bytes。
可能由于内存对齐的缘故, PyLongObject 实际占用了 32bytes 而不是 28bytes。
mimzy
2018-06-17 23:31:58 +08:00
首先更正小整数对象池 small_ints 的区间应该是 [-5, 257),我正文里写错了。

@geelaw #3 因为不熟悉 C,我目前只能大概理解你后面的意思,之后我会再好好想想。关于假设连续对象的地址接近这个问题,在 [-5, 257) 内的各个数都是差 32,应该是因为先开辟出一整块空间( PyIntBlock ),然后构建的链表,所以是连续的(我刚刚找到的链接里有提到)。然而验证又涉及到阅读具体实现的 C 源代码,看来还是绕不开…

@future0906 #4 「小整数」这个关键词找到了有用的信息,我之前知道是被优化了的,但好像搜的关键词不太对

现在看到了这个 https://foofish.net/python_int_implement.html
mimzy
2018-06-17 23:34:32 +08:00
@jmc891205 #5 非常感谢!!
jmc891205
2018-06-17 23:54:38 +08:00
@mimzy
1. 小整数缓存用的是数组不是链表
2. 6 楼最后提到的链接是 Python2 的实现
copie
2018-06-18 00:04:23 +08:00
由于我写的比较多,而且回复没有 md 所以我开辟了一个主题
https://www.v2ex.com/t/463799#reply0
wwqgtxx
2018-06-18 00:07:42 +08:00
其实探讨一下这个问题会更有趣
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000002)-id(100000000000000000000000)
-40
>>> id(100000000000000000000001)-id(100000000000000000000000)
-40
zhouheyang0919
2018-06-18 00:23:54 +08:00
@wwqgtxx

>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-120
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-40
>>> id(100000000000000000000003)-id(100000000000000000000000)
-80

-40 似乎是内存分配器正好分配了连续的内存空间而产生的巧合。
wwqgtxx
2018-06-18 00:35:20 +08:00
@zhouheyang0919 其实并没有那么简单,你再看看下面的几行输出
>>> id(100000000000000000000000)
1932290411216
>>> id(100000000000000000000001)
1932290411176
>>> id(100000000000000000000002)
1932290411136
>>> id(100000000000000000000003)
1932290411096
zhouheyang0919
2018-06-18 00:50:13 +08:00
@wwqgtxx

>>> id(100000000000000000000000)
139793637212488
>>> id(100000000000000000000000)
139793637212448
>>> id(100000000000000000000000)
139793637212408
>>> id(100000000000000000000000)
139793637212368

所以呢
wwqgtxx
2018-06-18 01:03:30 +08:00
@zhouheyang0919 只是说这个问题就很有趣了,涉及到表达式执行顺序,内存分配器策略和垃圾回收时机了,只要频率够高还能跑出来更加诡异的数字
>>> id(100000000000000000000001)-id(100000000000000000000000)
-1160
>>> id(100000000000000000000001)-id(100000000000000000000000)
-160
>>> id(100000000000000000000001)-id(100000000000000000000000)
-1000
>>> id(100000000000000000000001)-id(100000000000000000000000)
1160
>>> id(100000000000000000000001)-id(100000000000000000000000)
1120
>>> id(100000000000000000000001)-id(100000000000000000000000)
1200

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/463777

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX