为什么在Python中,整数需要三倍的内存空间?

34
在64位系统中,Python的整数占用24个字节。相比于C语言在64位系统中只需8个字节来存储整型变量,Python整数占用的内存是其3倍之多。这是因为Python的整数是对象所致。那么,这额外的内存用于什么呢?我有我的猜测,但最好知道确切原因。

3
请查看以下文章:http://www.laurentluce.com/posts/python-integer-objects-implementation/,以及https://docs.python.org/2/c-api/structures.html,了解Python中常见的对象结构。 - DNA
1
@DNA:这里讨论的是Python 2基本的“int”类型;而在Python 2中,替代“int”类型的“long”类型则更加复杂。在Python 3中,“long”类型已经被废除。 - Martijn Pieters
1
请注意,此问题及其答案仅适用于CPython参考实现。其他实现可能具有完全不同的内存使用情况(尽管相同的一般原则适用:需要存储大小和其他对象元数据)。 - Bob
2个回答

42

请记住,Python int 类型不像 C 的 int 类型一样有限制范围;唯一的限制是可用内存。

内存用于存储值,当前整数存储的大小(存储大小可变以支持任意大小),以及标准 Python 对象记录(相关对象的引用和引用计数)。

您可以查看longintrepr.h源代码(Python 3 中的 int 类型在 Python 2 中传统上被称为 long 类型);它有效地使用了PyVarObject C 类型来跟踪整数大小:

struct _longobject {
        PyObject_VAR_HEAD
        digit ob_digit[1];
};

ob_digit数组存储的是15或30位宽度的“数字”(取决于您的平台);因此在我的64位OS X系统上,最大整数为(2 ^ 30) - 1使用了1个“digit”:

>>> sys.getsizeof((1 << 30) - 1)
28

但如果您在数字中使用2个30位数字,则需要额外的4个字节,等等:

>>> sys.getsizeof(1 << 30)
32
>>> sys.getsizeof(1 << 60)
36
>>> sys.getsizeof(1 << 90)
40

首先,这24个字节是 PyObject_VAR_HEAD 结构,它保存对象的大小、引用计数和类型指针(在我64位OS X平台上每个8字节/64位)。

在Python 2中,小于等于sys.maxint但大于等于-sys.maxint -1 的整数使用更简单的结构仅存储单个值:

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

由于使用了PyObject而不是PyVarObject,因此在结构体中没有ob_size字段,并且内存大小仅限于24个字节;8个用于long值,8个用于引用计数和8个用于类型对象指针。


1
如果将int作为数字序列,负值如何处理?Python中是否有二进制补码的概念?如果我打印hex(-1),我会得到-0x1,或者如果我打印bin(-1),我会得到-0b1。我理解这可能不是内部表示的内容,但是如果高位未设置,Python如何决定它是负值? - Har
1
@Har:对象大小设置为负值。请参见链接的头文件负数用ob_size < 0表示。因此,如果整数表示需要2个ob_digits条目,则ob_size2-2,后者表示它是负整数。 - Martijn Pieters
1
那意味着它不是二进制补码,而只是结构中表示它是否为负数的一个位? - Har
2
@Har:没错;内部表示不使用二进制补码。 - Martijn Pieters
每个“数字”中剩余的1或2位会发生什么?(由于使用16或32位来存储只有15或30位的数字) - PieterNuyts
显示剩余12条评论

4

从longintrepr.h文件中,我们可以看到Python的'int'对象是使用以下C结构定义的:

struct _longobject {
        PyObject_VAR_HEAD
        digit ob_digit[1];
};

Digit是一个32位无符号值。大部分空间由可变大小的对象头占用。从object.h中,我们可以找到它的定义:

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

我们可以看到,在存储值中,“数字”计数的Py_ssize_t使用了64位,假设是64位系统。这可能有些浪费。我们还可以看到,通用对象头具有64位引用计数和指向对象类型的指针,这也将是64位的存储空间。引用计数是Python判断何时释放对象的必要条件,对象类型的指针是必要的,以便知道我们拥有一个整数而不是字符串,因为C结构没有办法从任意指针测试对象的类型。
在大多数Python版本中,_PyObject_HEAD_EXTRA被定义为空,但如果启用该选项,则可以使用另外两个64位指针来存储堆上所有Python对象的链接列表。

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