V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
mimzy
V2EX  ›  Python

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

  •  
  •   mimzy ·
    mookrs · 2018-06-17 21:47:23 +08:00 · 2940 次点击
    这是一个创建于 2352 天前的主题,其中的信息可能已经有所发展或是发生改变。

    测试环境 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 么?

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

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

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

    后面 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
        4
    future0906  
       2018-06-17 22:56:31 +08:00   ❤️ 1
    CPython 里面所有的小整数都是被预初始化,做成一个池的。
    jmc891205
        5
    jmc891205  
       2018-06-17 23:06:29 +08:00   ❤️ 1
    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
        6
    mimzy  
    OP
       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
        7
    mimzy  
    OP
       2018-06-17 23:34:32 +08:00
    @jmc891205 #5 非常感谢!!
    jmc891205
        8
    jmc891205  
       2018-06-17 23:54:38 +08:00   ❤️ 1
    @mimzy
    1. 小整数缓存用的是数组不是链表
    2. 6 楼最后提到的链接是 Python2 的实现
    copie
        9
    copie  
       2018-06-18 00:04:23 +08:00   ❤️ 1
    由于我写的比较多,而且回复没有 md 所以我开辟了一个主题
    https://www.v2ex.com/t/463799#reply0
    wwqgtxx
        10
    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
        11
    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
        12
    wwqgtxx  
       2018-06-18 00:35:20 +08:00
    @zhouheyang0919 其实并没有那么简单,你再看看下面的几行输出
    >>> id(100000000000000000000000)
    1932290411216
    >>> id(100000000000000000000001)
    1932290411176
    >>> id(100000000000000000000002)
    1932290411136
    >>> id(100000000000000000000003)
    1932290411096
    zhouheyang0919
        13
    zhouheyang0919  
       2018-06-18 00:50:13 +08:00
    @wwqgtxx

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

    所以呢
    wwqgtxx
        14
    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
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3346 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:11 · PVG 20:11 · LAX 04:11 · JFK 07:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.