为什么这个Python逻辑索引占用这么多内存
我有一个一维的时间序列,长度是1亿(也就是100,000,000个元素)。这里是我在Dropbox上使用的数据链接。(这个文件有382 MB。)
更新
根据内存分析,错误发生在这一行:
data[absolute(data-dc)< m*std(data)]=dc.
更具体来说,操作absolute(data-dc)
消耗了所有的内存。data
就是上面提到的那个,而dc
是一个常量。也许这是一个微妙的语法错误?
我想要去掉一些异常值和伪影,并用中位数替换这些值。我尝试用以下函数来实现这个目标。
from numpy import *
from sys import argv
from scipy.io import savemat
from scipy.stats import scoreatpercentile
def reject_outliers(data,dc,m=3):
data[data==0] = dc
data[bp.absolute(data-dc) < m*np.std(data)] = dc
return data
def butter_bandpass(lowcut,highcut,fs,order=8):
nyq = 0.5*fs
low = lowcut/nyq
high = highcut/nyq
b,a= butter(order, [low, high], btype='band')
return b,a
def butter_bandpass_filter(data,lowcut,highcut,fs,order=8):
b,a = butter_bandpass(lowcut,highcut,fs,order=order)
return lfilter(b,a,data)
OFFSET = 432
filename = argv[1]
outname = argv[2]
print 'Opening '+ filename
with open(filename,'rb') as stream:
stream.seek(OFFSET)
data=fromfile(stream,dtype='int16')
print 'Removing Artifacts, accounting for zero-filling'
dc = median(data)
data = reject_outliers(data,dc)
threshold = scoreatpercentile(absolute(data),85)
print 'Filtering and Detrending'
data = butter_bandpass_filter(data,300,7000,20000)
savemat(outname+'.mat',mdict={'data':data})
调用这个函数处理一个文件时,消耗了4 GB的内存和3 GB的虚拟内存。我确信问题出在这个函数的第二行,因为我一步步调试我写的脚本,发现它总是在这一部分卡住。我甚至可以在OS X的Finder中看到可用的硬盘空间每秒都在急剧下降。
时间序列的长度并不足以解释这个问题。那么reject-outliers
的第二行到底出了什么问题呢?
3 个回答
这是你数据和修改过的 @mbatchkarov的代码 的结果:
$ python mbatchkarov.py
Filename: mbatchkarov.py
Line # Mem usage Increment Line Contents
================================================
5 @profile
6 15.74 MB 0.00 MB def go(m=3):
7 15.74 MB 0.00 MB header_size = 432
8 15.74 MB 0.00 MB with open('ch008.ddt', 'rb') as file:
9 15.75 MB 0.00 MB file.seek(header_size)
10 380.10 MB 364.36 MB data = np.fromfile(file, dtype=np.int16) # 2 bytes per item
11 380.20 MB 0.10 MB dc = np.median(data)
12
13 # data[np.absolute(data - dc) < m*np.std(data)] = dc
14 # `data - dc` => temporary array 8 bytes per item
15 744.56 MB 364.36 MB t = data.copy()
16 744.66 MB 0.09 MB t -= dc
17 744.66 MB 0.00 MB np.absolute(t, t)
18 926.86 MB 182.20 MB b = t < m*np.std(data) # boolean => 1 byte per item
19 926.87 MB 0.01 MB data[b] = dc
20 926.87 MB 0.00 MB return data
data - dc
这个操作会需要更多的内存:200M个数据项,每个数据项占8个字节,也就是说,data - dc
会因为广播的原因创建一个或两个临时的双精度数组。为了避免这种情况,可以先明确地复制一份数据,然后在原地进行减法操作:
t = data.copy() # 200M items x 2 bytes per item
t -= dc
看起来 memory_profiler
并没有显示临时数组的内存使用情况。程序的最大内存使用量大约是3GB。
我刚刚生成了1亿个随机的小数,并且做了你提到的那种索引。整个过程的内存使用量一直都在1GB以下。你的代码还有其他我们不知道的操作吗?可以试试用一个很棒的工具memory_profiler来运行你的代码,看看内存使用情况。
编辑:我添加了代码和memory_profiler的输出:
from numpy.random import uniform
import numpy
@profile
def go(m=3):
data = uniform(size=100000000)
dc = numpy.median(data)
data[numpy.absolute(data-dc) < m*numpy.std(data)] = dc
return data
if __name__ == '__main__':
go()
输出:
Filename: example.py
Line # Mem usage Increment Line Contents
================================================
3 @profile
4 15.89 MB 0.00 MB def go(m=3):
5 778.84 MB 762.95 MB data = uniform(size=100000000)
6 778.91 MB 0.06 MB dc = numpy.median(data)
7 874.34 MB 95.44 MB data[numpy.absolute(data-dc) < m*numpy.std(data)] = dc
8 874.34 MB 0.00 MB return data
正如你所看到的,1亿个小数并没有占用那么多内存。
Memory_profiler 记录的是在执行某一行代码之后,Python 虚拟机的内存状态。因此,如果在一行代码中创建和销毁的数组,它们不会出现在内存使用情况的报告中。
以 @mbatchkarov 的例子为例,你可以把 "data[numpy.absolute(data-dc) < m*numpy.std(data)] = dc" 这一行拆分成更小的部分,这样就能看到临时数组对内存的影响:
from numpy.random import uniform
import numpy
@profile
def go(m=3):
data = uniform(size=100000000)
dc = numpy.median(data)
t1 = data-dc
t2 = numpy.absolute(t1) < m*numpy.std(data)
data[t2] = dc
return data
if __name__ == '__main__':
go()
这样就能得到
$ python -m memory_profiler t1.py
Filename: t1.py
Line # Mem usage Increment Line Contents
================================================
4 @profile
5 16.61 MB 0.00 MB def go(m=3):
6 779.56 MB 762.95 MB data = uniform(size=100000000)
7 779.62 MB 0.06 MB dc = numpy.median(data)
8 1542.57 MB 762.95 MB t1 = data-dc
9 1637.99 MB 95.42 MB t2 = numpy.absolute(t1) < m*numpy.std(data)
10 1638.00 MB 0.02 MB data[t2] = dc
11 1638.00 MB 0.00 MB return data
在这里很明显,"data-dc" 这一操作会重复占用内存。解决这个问题的方法是直接在原数组上进行减法,也就是说,把 "t1 = data - dc" 改成 "data -= dc":
$ python -m memory_profiler t1.py
Filename: t1.py
Line # Mem usage Increment Line Contents
================================================
4 @profile
5 16.61 MB 0.00 MB def go(m=3):
6 779.56 MB 762.95 MB data = uniform(size=100000000)
7 779.62 MB 0.06 MB dc = numpy.median(data)
8 779.63 MB 0.01 MB data -= dc
9 875.05 MB 95.42 MB t2 = numpy.absolute(data) < m*numpy.std(data)
10 875.07 MB 0.02 MB data[t2] = dc
11 875.07 MB 0.00 MB return data
如你所见,"data -= dc" 现在几乎不会增加内存使用。