【问题标题】:Memory consumption in Python - lists, subscripting, and pointersPython 中的内存消耗 - 列表、下标和指针
【发布时间】: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

注意事项:

  1. 输出 (2) 有意义。一个 int8 是 8 位(一个字节),一亿字节是 100 MB
  2. 输出 (3) 我不明白:
    1. 第一个问题是 sys.getsizeof() 显示对象占用了 900 MB,但 psutil 显示该进程现在占用了 3,239 MB 以上的内存 (3526-287=3239)。这种幻像内存使用量来自哪里?
    2. 900 MB 从何而来? (从Python: Size of Reference? 开始,我假设有 100 MB 的 numpy 对象加上 1 亿个指针,每个指针有 8 个字节,所以 100 MB + 800 MB = 900 MB?)
  3. 输出 (4) 有意义。 4 亿个 int8 为 400 MB。
  4. 输出 (5) 我不明白。是复制还是参考?如果引用,我们只引用 1 亿行,对吗?这怎么会变成 400 MB?

谢谢

【问题讨论】:

  • 搜索 [numpy] getsizeof 以查看以前的讨论。如果您不了解数组和列表的存储方式,这种测试可能会令人困惑。 getsizeof 在应用于 numpy 数组时有些用处——前提是您了解 copyview 之间的区别。查找列表时没用。

标签: python-3.x numpy memory profiling


【解决方案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 应该会显示数组大小的有序增加。

【讨论】:

    猜你喜欢
    • 2013-06-13
    • 2017-02-16
    • 1970-01-01
    • 2013-03-16
    • 2019-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-05
    相关资源
    最近更新 更多