内存中的数据大小与磁盘上的数据大小

19

在内存中存储数据所需的RAM与在文件中存储相同数据所需的磁盘空间相比如何?是否存在一般化的相关性?

例如,假设我有10亿个浮点值。以二进制形式存储,它将占用4亿字节或3.7GB的磁盘空间(不包括标头等)。然后假设我将这些值读入Python列表中...我应该期望需要多少RAM?


更多的RAM!除此之外,还有列表开销等问题。如果你担心的话,a)找出原因,b)考虑只将原始数据存储在内存中,并在运行时解压缩它(这取决于你对数据的处理方式)。 - Ry-
2
相关链接:https://dev59.com/8nNA5IYBdhLWcg3wWseK#994010 - Ashwini Chaudhary
我的第一反应是用户需要等待一段时间,直到所有数据都加载到RAM中。 - Mike Weber
1
我的第一个想法是,为什么你不使用mmap呢? - Ignacio Vazquez-Abrams
无论是在RAM还是磁盘中,您使用的字节数都与您要使用的完全相同(尽管这种请求可能深藏在库中),除了{文件系统,内存管理器}的元数据之外,这些元数据很难进行比较或量化,并且很少有意义。 - user395760
2个回答

13

Python对象数据大小

如果数据存储在某个Python对象中,那么实际数据在内存中会有更多的相关数据。

这很容易进行测试。

不同形式数据的大小

值得注意的是,对于小数据,Python对象的开销非常显著,但很快就变得可以忽略不计。

以下是用于生成图表的iPython代码:

%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')

2
这个答案是不正确的。sys.getsizeof的文档说明:“只计算直接归因于对象的内存消耗,而不计算它所引用的对象的内存消耗。”因此,您只考虑了容器中分配的内存,而没有考虑为数字对象本身分配的额外内存。 - Jan Špaček
1
你有什么建议来确定完整的内存分配吗?我会重新绘制图表! - tmthydvnprt
我认为你需要将listsettuple的报告内存大小增加len(double_list) * sys.getsizeof(1.0)。可能还需要一些额外的内存来管理分配,但我不知道如何测量它,而且应该是可以忽略不计的。 - Jan Špaček

4
在普通的Python列表中,每个双精度数字至少需要32个字节的内存,但实际上只有8个字节用于存储实际数字,其余的是为了支持Python的动态特性而必需的。
在CPython中使用的float对象在floatobject.h中定义:
typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

其中PyObject_HEAD一个宏,用于扩展PyObject结构体:

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

因此,Python中的每个浮点对象除了8字节的double之外还存储两个指针大小的字段(因此在64位架构上每个字段占用8字节),每个数字需要24字节的堆分配内存。这可以通过sys.getsizeof(1.0) == 24进行确认。
这意味着,在Python中,一个由n个双精度数组成的列表需要至少8*n字节的内存来存储指向数字对象的指针(PyObject*),而每个数字对象需要额外的24字节。要测试它,请尝试在Python REPL中运行以下代码:
>>> import math
>>> list_of_doubles = [math.sin(x) for x in range(10*1000*1000)]

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

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

如果列表中的所有元素都指向浮点数1.0的同一实例,则您将只获得约80 MB。


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