Python 中的内存消耗 - 列表、下标和指针
Posted
技术标签:
【中文标题】Python 中的内存消耗 - 列表、下标和指针【英文标题】:Memory consumption in Python - lists, subscripting, and pointers 【发布时间】:2019-12-09 18:21:46 【问题描述】:我试图了解 Python 对象使用了多少内存。
在下面的代码中,我检查了 numpy 数组与列表以及下标 numpy 数组的内存:
import sys, os, psutil, numpy as np
def size_of(obj):
return f'sys.getsizeof(obj) / 1000000:,.0f MB'
def get_memory_usage():
process = psutil.Process(os.getpid())
return f'process.memory_info().rss / 1000000:,.0f MB'
# Numpy vs List
print(f'(1) Mem usage: get_memory_usage()')
ONE_HUNDRED_MIL_NP = np.random.randint(-128,127,int(10**8),dtype='int8')
print(f'(2) Mem usage: get_memory_usage(), ONE_HUNDRED_MIL_NP: size_of(ONE_HUNDRED_MIL_NP)')
ONE_HUNDRED_MIL_LIST = list(np.random.choice(127, int(10**8), replace=True).astype('int8'))
print(f'(3) Mem usage: get_memory_usage(), ONE_HUNDRED_MIL_LIST: size_of(ONE_HUNDRED_MIL_LIST)')
# Now try subscriping
FOURCOLS = np.random.randint(-128,127,size=(int(10**8),4),dtype='int8')
print(f'(4) Mem usage: get_memory_usage(), FOURCOLS: size_of(FOURCOLS)')
FOURCOLS_PERMUTED = FOURCOLS[np.random.randint(0,len(FOURCOLS),size=len(FOURCOLS),dtype='int32')]
print(f'(5) Mem usage: get_memory_usage(), FOURCOLS_PERMUTED: size_of(FOURCOLS_PERMUTED)')
这会返回:
(1) Mem usage: 187 MB
(2) Mem usage: 287 MB, ONE_HUNDRED_MIL_NP: 100 MB
(3) Mem usage: 3,526 MB, ONE_HUNDRED_MIL_LIST: 900 MB
(4) Mem usage: 3,926 MB, FOURCOLS: 400 MB
(5) Mem usage: 4,326 MB, FOURCOLS_PERMUTED: 400 MB
注意事项:
-
输出 (2) 有意义。一个 int8 是 8 位(一个字节),一亿字节是 100 MB
输出 (3) 我不明白:
-
第一个问题是 sys.getsizeof() 显示对象占用了 900 MB,但 psutil 显示该进程现在占用了 3,239 MB 以上的内存 (3526-287=3239)。这种幻像内存使用量来自哪里?
900 MB 从何而来? (从Python: Size of Reference? 开始,我假设有 100 MB 的 numpy 对象加上 1 亿个指针,每个指针有 8 个字节,所以 100 MB + 800 MB = 900 MB?)
谢谢
【问题讨论】:
搜索[numpy] getsizeof
以查看以前的讨论。如果您不了解数组和列表的存储方式,这种测试可能会令人困惑。 getsizeof
在应用于 numpy 数组时有些用处——前提是您了解 copy
和 view
之间的区别。查找列表时没用。
【参考方案1】:
ONE_HUNDRED_MIL_NP = np.random.randint(-128,127,int(10**8),dtype='int8')
这构成了一个数组。 ONE_HUNDRED_MIL_NP.nbytes
是衡量数组大小的好方法。数组有一些基本信息,如形状、步幅、dtype,但大部分空间是包含字节的一维数据缓冲区,在这种情况下是每个元素的字节。
ONE_HUNDRED_MIL_LIST = list(np.random.choice(127, int(10**8), replace=True).astype('int8'))
这会从该数组中生成一个列表。列表有一个数据缓冲区,其中包含对内存中其他对象的引用。 getsizeof
只是测量该缓冲区的大小,而对对象只字未提。这些对象是从数组中提取的numpy.int8
对象。它们实际上并不引用数组的元素,而是这些值的副本。
从数组中获取列表的更好方法是使用arr.tolist()
。
FOURCOLS = np.random.randint(-128,127,size=(int(10**8),4),dtype='int8')
这只是另一个数组。 2d 形状不会改变它占用的内存量。
FOURCOLS_PERMUTED = FOURCOLS[np.random.randint(0,len(FOURCOLS),size=len(FOURCOLS),dtype='int32')]
这是advanced indexing
的示例。它使用自己的数据缓冲区创建一个新数组(不是带有共享缓冲区的view
)。是的,您只是索引第一个维度,但数据缓冲区存储所有值,而不是对 FOURCOLS 行的引用。
列表列表确实存储引用或指向嵌套列表。所以改组外部列表只会改组引用。多维c
数组还存储引用或指针。但是多维 numpy 数组使用不同的模型。数据是一个平面 c
数组。多维性由shape/strides
迭代代码产生。
所以看看你的数字:
(1) Mem usage: 187 MB
基本用法。
(2) Mem usage: 287 MB, ONE_HUNDRED_MIL_NP: 100 MB
向基数增加 100mb。
(3) Mem usage: 3,526 MB, ONE_HUNDRED_MIL_LIST: 900 MB
900 大致是列表的数据缓冲区使用的内存。总使用量增加的其余部分是用于这 10**8 个np.int8
对象的存储。
(4) Mem usage: 3,926 MB, FOURCOLS: 400 MB
这显示了另外 400 MB 的内存使用情况。
(5) Mem usage: 4,326 MB, FOURCOLS_PERMUTED: 400 MB
还有 400 个。
如果没有 (3) 列表创建 mem usage
应该会显示数组大小的有序增加。
【讨论】:
以上是关于Python 中的内存消耗 - 列表、下标和指针的主要内容,如果未能解决你的问题,请参考以下文章
使用 Requests HTTP 库了解 python 中的内存消耗增加