Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/291.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
加载的pickle数据在内存中比磁盘上大得多,并且似乎会泄漏。(Python 2.7)_Python_Python 2.7_Numpy_Pickle - Fatal编程技术网

加载的pickle数据在内存中比磁盘上大得多,并且似乎会泄漏。(Python 2.7)

加载的pickle数据在内存中比磁盘上大得多,并且似乎会泄漏。(Python 2.7),python,python-2.7,numpy,pickle,Python,Python 2.7,Numpy,Pickle,我的记忆力有问题。我有一个pickle文件,是我用Python2.7Cpickle模块编写的。该文件在磁盘上为2.2GB。它包含字典、列表和numpy数组的各种嵌套的字典 当我加载这个文件时(在Python2.7上再次使用cPickle),Python进程最终使用了5.13GB的内存。然后,如果删除对加载数据的引用,则数据使用量将减少2.79GB。在程序结束时,还有另一个2.38GB尚未清理 cPickle是否在后端保留了一些缓存或备忘录表?这些额外的数据来自哪里?有没有办法清除它 加载的cPi

我的记忆力有问题。我有一个pickle文件,是我用Python2.7Cpickle模块编写的。该文件在磁盘上为2.2GB。它包含字典、列表和numpy数组的各种嵌套的字典

当我加载这个文件时(在Python2.7上再次使用cPickle),Python进程最终使用了5.13GB的内存。然后,如果删除对加载数据的引用,则数据使用量将减少2.79GB。在程序结束时,还有另一个2.38GB尚未清理

cPickle是否在后端保留了一些缓存或备忘录表?这些额外的数据来自哪里?有没有办法清除它

加载的cPickle中没有自定义对象,只有dict、list和numpy数组。我不明白它为什么会这样


令人不快的例子 下面是我为演示该行为而编写的一个简单脚本:

from six.moves import cPickle as pickle
import time
import gc
import utool as ut

print('Create a memory tracker object to snapshot memory usage in the program')
memtrack = ut.MemoryTracker()

print('Print out how large the file is on disk')
fpath = 'tmp.pkl'
print(ut.get_file_nBytes_str('tmp.pkl'))

print('Report memory usage before loading the data')
memtrack.report()
print(' Load the data')
with open(fpath, 'rb') as file_:
    data = pickle.load(file_)

print(' Check how much data it used')
memtrack.report()

print(' Delete the reference and check again')
del data
memtrack.report()

print('Check to make sure the system doesnt want to clean itself up')

print(' This never does anything. I dont know why I bother')
time.sleep(1)
gc.collect()
memtrack.report()

time.sleep(10)
gc.collect()

for i in range(10000):
    time.sleep(.001)

print(' Check one more time')
memtrack.report()
这是它的输出

Create a memory tracker object to snapshot memory usage in the program
[memtrack] +----
[memtrack] | new MemoryTracker(Memtrack Init)
[memtrack] | Available Memory = 12.41 GB
[memtrack] | Used Memory      = 39.09 MB
[memtrack] L----
Print out how large the file is on disk
2.00 GB
Report memory usage before loading the data
[memtrack] +----
[memtrack] | diff(avail) = 0.00 KB
[memtrack] | [] diff(used) = 12.00 KB
[memtrack] | Available Memory = 12.41 GB
[memtrack] | Used Memory      = 39.11 MB
[memtrack] L----
 Load the data
 Check how much data it used
[memtrack] +----
[memtrack] | diff(avail) = 5.09 GB
[memtrack] | [] diff(used) = 5.13 GB
[memtrack] | Available Memory = 7.33 GB
[memtrack] | Used Memory      = 5.17 GB
[memtrack] L----
 Delete the reference and check again
[memtrack] +----
[memtrack] | diff(avail) = -2.80 GB
[memtrack] | [] diff(used) = -2.79 GB
[memtrack] | Available Memory = 10.12 GB
[memtrack] | Used Memory      = 2.38 GB
[memtrack] L----
Check to make sure the system doesnt want to clean itself up
 This never does anything. I dont know why I bother
[memtrack] +----
[memtrack] | diff(avail) = 40.00 KB
[memtrack] | [] diff(used) = 0.00 KB
[memtrack] | Available Memory = 10.12 GB
[memtrack] | Used Memory      = 2.38 GB
[memtrack] L----
 Check one more time
[memtrack] +----
[memtrack] | diff(avail) = -672.00 KB
[memtrack] | [] diff(used) = 0.00 KB
[memtrack] | Available Memory = 10.12 GB
[memtrack] | Used Memory      = 2.38 GB
[memtrack] L----

健全性检查1(垃圾收集) 这里的健全性检查是一个脚本,它分配相同数量的数据,然后将其删除,因此进程会完美地自我清理

以下是脚本:

import numpy as np
import utool as ut

memtrack = ut.MemoryTracker()
data = np.empty(2200 * 2 ** 20, dtype=np.uint8) + 1
print(ut.byte_str2(data.nbytes))
memtrack.report()
del data
memtrack.report()
这是输出

[memtrack] +----
[memtrack] | new MemoryTracker(Memtrack Init)
[memtrack] | Available Memory = 12.34 GB
[memtrack] | Used Memory      = 39.08 MB
[memtrack] L----
2.15 GB
[memtrack] +----
[memtrack] | diff(avail) = 2.15 GB
[memtrack] | [] diff(used) = 2.15 GB
[memtrack] | Available Memory = 10.19 GB
[memtrack] | Used Memory      = 2.19 GB
[memtrack] L----
[memtrack] +----
[memtrack] | diff(avail) = -2.15 GB
[memtrack] | [] diff(used) = -2.15 GB
[memtrack] | Available Memory = 12.34 GB
[memtrack] | Used Memory      = 39.10 MB
[memtrack] L----

健全性检查2(确保类型) 只需做一个完整性检查,以确保此列表中没有自定义类型。这是此结构中出现的类型集。 数据本身是具有以下键的dict: ['maws_列表'、'int_rvec'、'wx_列表'、'aid_to_idx'、'agg_标志'、'agg_rvecs'、'gamma_列表'、'wx_to_idf'、'aids'、'fxs_列表'、'wx_to_aids']。 以下脚本特定于此结构的特定嵌套,但它详尽地显示了此容器中使用的类型:

print(data.keys())
type_set = set()
type_set.add(type(data['int_rvec']))
type_set.add(type(data['wx_to_aids']))
type_set.add(type(data['wx_to_idf']))
type_set.add(type(data['gamma_list']))
type_set.update(set([n2.dtype for n1 in  data['agg_flags'] for n2 in n1]))
type_set.update(set([n2.dtype for n1 in  data['agg_rvecs'] for n2 in n1]))
type_set.update(set([n2.dtype for n1 in  data['fxs_lists'] for n2 in n1]))
type_set.update(set([n2.dtype for n1 in  data['maws_lists'] for n2 in n1]))
type_set.update(set([n1.dtype for n1 in  data['wx_lists']]))
type_set.update(set([type(n1) for n1 in  data['aids']]))
type_set.update(set([type(n1) for n1 in  data['aid_to_idx'].keys()]))
type_set.update(set([type(n1) for n1 in  data['aid_to_idx'].values()]))
set类型的输出为

{bool,
 dtype('bool'),
 dtype('uint16'),
 dtype('int8'),
 dtype('int32'),
 dtype('float32'),
 NoneType,
 int}
这表明所有序列最终都解析为None,一个标准python类型或一个标准numpy类型。你必须相信我,iterable类型都是列表和dict


简言之,我的问题是:

  • 为什么加载2GB的pickle文件最终会在RAM中使用5GB的内存
  • 当最近加载的数据被垃圾收集时,为什么只有2.5GB/5GB被清理
  • 有什么办法可以回收这些丢失的内存吗

这里一个可能的罪魁祸首是,Python在设计上过度分配了数据结构,如列表和字典,以加快对它们的附加,因为内存分配很慢。例如,在32位Python上,空字典的
sys.getsizeof()
为36字节。添加一个元素,它将变成52个字节。它保留52个字节,直到它有五个元素,此时它变成68个字节。因此,很明显,当您添加第一个元素时,Python为四个元素分配了足够的内存,然后当您添加第五个元素(LEELOO DALLAS)时,它又为四个元素分配了足够的内存。随着列表的增长,添加的填充量增长得越来越快:实际上,每次填充时,列表的内存分配都会翻倍

因此,我预计会有类似的情况发生,因为pickle协议似乎不存储pickle对象的长度,至少对于Python数据类型来说是这样的,所以它本质上是一次读取一个列表或字典项并将其追加,Python在添加项时会像上面所描述的那样增长对象。根据取消勾选数据时对象大小的变化情况,列表和字典中可能会有很多额外的空间。(但是,不确定如何存储
numpy
对象;它们可能更紧凑。)

还可能会分配一些临时对象,这有助于解释内存使用量是如何变得如此之大的

现在,当您复制一个列表或字典时,Python确切地知道它有多少项,并且可以为副本分配正确的内存量。如果一个假设的5元素列表
x
被分配了68个字节,因为它预计将增长到8个元素,那么副本
x[:]
被分配了56个字节,因为这正是正确的数量。因此,你可以在加载后用一个更大的对象进行拍摄,看看它是否有明显的帮助

但可能不会。当对象被销毁时,Python不一定会将内存释放回操作系统。相反,它可能会保留内存,以防需要分配更多同类对象(这很有可能),因为重用您已有的内存比释放内存后重新分配内存的成本更低。因此,尽管Python可能没有将内存还给操作系统,但这并不意味着存在漏洞。它可供脚本的其余部分使用,但操作系统无法看到它。在这种情况下,没有办法强制Python将其返回

我不知道什么是
utool
(我找到了一个同名的Python包,但它似乎没有
MemoryTracker
类),但根据它的测量结果,它可能显示了操作系统对它的看法,而不是Python。在本例中,您看到的基本上是脚本的最大内存使用量,因为Python会保留该内存,以防您需要它来做其他事情。如果您从未使用过它,它最终将被操作系统替换掉,物理RAM将被提供给其他需要它的进程

总之,脚本使用的内存量本身并不是一个需要解决的问题,一般来说,您也不需要担心。(这就是为什么您首先要使用Python!)您的脚本工作正常吗?它运行得够快吗?那你就没事了。Python和NumPy都是成熟且广泛使用的软件;在像
pickle
库这样经常使用的东西中,找到这种大小的、以前未被检测到的真实内存泄漏的可能性非常小

如果可用,比较脚本的备忘录会很有趣