内存中的数据大小与磁盘上的数据大小
内存中存储数据所需的RAM和将相同数据存储在文件中所需的磁盘空间有什么区别吗?或者说没有一个通用的关系?
举个例子,假设我有十亿个浮点数。如果以二进制形式存储,这大约需要40亿个字节,也就是3.7GB的磁盘空间(不包括一些头信息之类的)。然后,如果我把这些数值读入Python中的一个列表……我应该预期这会需要多少RAM呢?
2 个回答
在普通的Python列表中,每个双精度数字至少需要32个字节的内存,但实际上只有8个字节用来存储这个数字,剩下的部分是为了支持Python的动态特性。
在CPython中使用的浮点对象定义在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字节的双精度数字外,还会存储两个指针大小的字段(在64位架构上,每个字段占8字节),这使得每个数字占用24字节的堆内存。你可以通过sys.getsizeof(1.0) == 24
来确认这一点。
这意味着,在Python中,一个包含n
个双精度数字的列表,光是存储指向数字对象的指针(PyObject*
)就需要至少8*n
字节的内存,而每个数字对象还需要额外的24字节。你可以在Python的交互式环境中运行以下代码来测试:
>>> 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)]
你会发现大约只有80 MB的内存使用,因为列表中的所有元素都指向同一个浮点数字1.0
的实例。
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')