关于python:内存与磁盘上的数据大小

Data size in memory vs. on disk

与在文件中存储相同数据所需的磁盘空间相比,在内存中存储数据所需的RAM如何?或者没有普遍的相关性?

例如,假设我只有十亿个浮点值。以二进制形式存储,即40亿字节或磁盘上的3.7GB(不包括头文件等)。然后假设我用python把这些值读到一个列表中…我需要多少内存?


python对象数据大小

如果数据存储在某个Python对象中,那么将有更多的数据附加到内存中的实际数据上。

这可能很容易测试。

The size of data in various forms

有趣的是,首先要注意的是,python对象的开销对于小数据是多么的重要,但是很快就会变得微不足道。

下面是用于生成绘图的IPython代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
%matplotlib inline
import random
import sys
import array
import matplotlib.pyplot as plt

max_doubles = 10000

raw_size = []
array_size = []
string_size = []
list_size = []
set_size = []
tuple_size = []
size_range = range(max_doubles)

# test double size
for n in size_range:
    double_array = array.array('d', [random.random() for _ in xrange(n)])
    double_string = double_array.tostring()
    double_list = double_array.tolist()
    double_set = set(double_list)
    double_tuple = tuple(double_list)

    raw_size.append(double_array.buffer_info()[1] * double_array.itemsize)
    array_size.append(sys.getsizeof(double_array))
    string_size.append(sys.getsizeof(double_string))
    list_size.append(sys.getsizeof(double_list))
    set_size.append(sys.getsizeof(double_set))
    tuple_size.append(sys.getsizeof(double_tuple))

# display
plt.figure(figsize=(10,8))
plt.title('The size of data in various forms', fontsize=20)
plt.xlabel('Data Size (double, 8 bytes)', fontsize=15)
plt.ylabel('Memory Size (bytes)', fontsize=15)
plt.loglog(
    size_range, raw_size,
    size_range, array_size,
    size_range, string_size,
    size_range, list_size,
    size_range, set_size,
    size_range, tuple_size
)
plt.legend(['Raw (Disk)', 'Array', 'String', 'List', 'Set', 'Tuple'], fontsize=15, loc='best')


在一个普通的python列表中,每个双精度数字至少需要32个字节的内存,但是只有8个字节用于存储实际的数字,其余的是支持python的动态特性所必需的。

cpython中使用的float对象在float object.h中定义:

1
2
3
4
typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

其中,PyObject_HEAD是一个扩展到PyObject结构的宏:

1
2
3
4
typedef struct _object {
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

因此,python中的每个浮点对象都存储两个指针大小的字段(因此每个字段在64位体系结构中占用8个字节),除了8个字节的double之外,每个数字还提供24个字节的堆分配内存。这是由sys.getsizeof(1.0) == 24确认的。

这意味着python中的n双精度列表至少需要8*n字节的内存来存储指向数字对象的指针(PyObject*),并且每个数字对象需要额外的24字节。要测试它,请尝试在python repl中运行以下行:

1
2
>>> import math
>>> list_of_doubles = [math.sin(x) for x in range(10*1000*1000)]

查看python解释器的内存使用情况(我在x86-64计算机上获得了大约350MB的分配内存)。请注意,如果您尝试:

1
>>> list_of_doubles = [1.0 for __ in range(10*1000*1000)]

您将获得大约80MB,因为列表中的所有元素都引用浮点数1.0的相同实例。