隐藏的Python内存泄漏

时间:2020-02-10 10:09:08

标签: python pandas numpy memory-leaks jupyter-notebook

在下面的代码中,在迭代c1 = 0和迭代c1 = 60之间,内存使用量大约增加了1G。你能帮我发现泄漏吗?

# M = 1000, N=18000
# data is a pandas dataframe with N rows and M columns (it is already loaded when this code begins).

mymat = np.zeros((M,M,N), dtype=bool)    
table = pd.Series(index=data.index, data=np.array(range(N))) 
data_std = data.std(axis=1)

for c1 in range(M):
    for c2 in range(M):
        if c1!=c2:
            mymat[c1,c2,list(
                table.loc[data.index[data.iloc[:,c1]-data.iloc[:,c2]>data_std]])] = True

1 个答案:

答案 0 :(得分:0)

实际上,这似乎根本不是内存泄漏,而是通过优化NumPy或您的操作系统为您提供的。 (就像您的操作系统一样,考虑到原始问题中代码的工作方式也存在特定于操作系统的差异。)

这是简化的example.py –基本思想是相同的。

import numpy as np
import os
import psutil

process = psutil.Process(os.getpid())

def print_memory(message):
    rss = process.memory_info().rss
    print(f"{message:30s} {rss // 1024:7d}kb")

M = N = 1000

print_memory('start')
#mymat = np.zeros((M, M, N), dtype=bool)
#mymat = (np.random.random((M, M, N)) > 0.5)
print_memory('generated')
for c1 in range(M):
    if c1 % 100 == 0:
        print_memory(f'iteration {c1}')
    for c2 in range(M):
        mymat[c1, c2, [1, 2, 3]] = True
print_memory(f'end')
print(mymat.nbytes, mymat.dtype, mymat.shape)

在启用第一行mymat(即生成零)的情况下运行此命令即可打印

$ python3 example.py
start                            23552kb
generated                        23564kb
iteration 0                      23564kb
iteration 100                   121340kb
iteration 200                   218996kb
iteration 300                   316652kb
iteration 400                   414320kb
iteration 500                   511976kb
iteration 600                   609632kb
iteration 700                   707288kb
iteration 800                   804944kb
iteration 900                   902600kb
end                            1000256kb
1000000000 bool (1000, 1000, 1000)

因此您可以看到,“生成” 1000 x 1000 x 1000阵列实际上并不会占用1 GB的内存。Numpy的nbytes表示确实如此,但是随着它开始充满非零值数据,就会发生分配。

使用第二个选项运行它,生成随机数据,结果如下:

$ python3 example.py
start                            23404kb
generated                      1000004kb
iteration 0                    1000004kb
iteration 100                  1000140kb
iteration 200                  1000148kb
iteration 300                  1000164kb
iteration 400                  1000164kb
iteration 500                  1000164kb
iteration 600                  1000164kb
iteration 700                  1000164kb
iteration 800                  1000164kb
iteration 900                  1000164kb
end                            1000164kb
1000000000 bool (1000, 1000, 1000)

如您所见,所有内存都立即分配,并且在循环过程中发生的很少内存增加实际上是舍入错误,并且可能仅是由于常规GC分配所致。 (np.ones()具有相同的行为。)

对于原始的1000 * 1000 * 18000数组,如果您实际上需要填充所有数组,最终将需要

>>> np.zeros((1000, 1000, 18000), dtype=bool).nbytes
18000000000

18 GB的内存。 (这也指出了Numpy对于布尔数组没有特定的打包数据类型的事实;每个布尔占用一个字节,而不仅仅是一个字节。这很可能是由于性能原因。)