Скрытая Python утечка памяти - PullRequest
0 голосов
/ 10 февраля 2020

В следующем коде использование памяти увеличивается примерно на 1G между итерацией c1 = 0 и итерацией c1 = 60. Можете ли вы помочь мне обнаружить утечку?

# 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 голосов
/ 12 февраля 2020

На самом деле это вовсе не утечка памяти, а оптимизация NumPy или ваша ОС дает вам. (Скорее всего, ваша ОС, учитывая наличие спецификаций ОС c в том, как ваш код работал в исходном вопросе.)

Вот упрощенный example.py - основная идея c такая же.

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 гигабайт памяти 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)

Как видите, вся память выделяется сразу, и то, что небольшое увеличение памяти происходит во время l oop, является практически ошибкой округления и, вероятно, просто из-за регулярных распределений G C. (np.ones() имеет такое же поведение.)

Что касается вашего исходного массива 1000 *1000* 18000, если вам действительно нужно заполнить весь массив, вам в конечном итоге понадобится

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

18 гигабайт памяти. (Это также указывает на тот факт, что Numpy не имеет определенного типа упакованных данных c для логических массивов; каждый логический тип занимает байт, а не только один бит. Это, вероятно, связано с соображениями производительности.)

...