Python列表/字典与numpy数组:性能与内存控制比较

10 投票
1 回答
14764 浏览
提问于 2025-04-16 11:27

我需要反复读取数据文件,并把数据存储到(numpy)数组里。我选择把数据存储在一个“数据字段”的字典中:{'field1': array1,'field2': array2,...}

情况 1(列表):

使用列表(或者collections.deque())来“添加”新的数据数组时,代码运行得很高效。但是,当我把存储在列表中的数组合并时,内存会增加,而且我没有办法再释放这些内存。举个例子:

filename = 'test'
# data file with a matrix of shape (98, 56)
nFields = 56
# Initialize data dictionary and list of fields
dataDict = {}

# data directory: each entry contains a list 
field_names = []
for i in xrange(nFields):
    field_names.append(repr(i))
    dataDict[repr(i)] = []

# Read a data file N times (it represents N files reading)
# file contains 56 fields of arbitrary length in the example
# Append each time the data fields to the lists (in the data dictionary)
N = 10000
for j in xrange(N):
    xy = np.loadtxt(filename)
    for i,field in enumerate(field_names):
        dataDict[field].append(xy[:,i])

# concatenate list members (arrays) to a numpy array 
for key,value in dataDict.iteritems():
    dataDict[key] = np.concatenate(value,axis=0)

计算时间: 63.4 秒
内存使用情况(顶部):13862 gime_se 20 0 1042m 934m 4148 S 0 5.8 1:00.44 python

情况 2(numpy数组):

每次读取numpy数组时直接进行合并,这样做效率不高,但内存保持在可控范围内。举个例子:

nFields = 56
dataDict = {}
# data directory: each entry contains a list 
field_names = []
for i in xrange(nFields):
    field_names.append(repr(i))
    dataDict[repr(i)] = np.array([])

# Read a data file N times (it represents N files reading)
# Concatenate data fields to numpy arrays (in the data dictionary)
N = 10000
for j in xrange(N):
    xy = np.loadtxt(filename)
    for i,field in enumerate(field_names):
        dataDict[field] = np.concatenate((dataDict[field],xy[:,i])) 

计算时间: 1377.8 秒
内存使用情况(顶部):14850 gime_se 20 0 650m 542m 4144 S 0 3.4 22:31.21 python

问题:

  • 有没有办法在保持情况 1的性能的同时,像情况 2那样控制内存?

  • 似乎在情况 1 中,当合并列表中的元素时,内存会增加(np.concatenate(value,axis=0))。有没有更好的方法来做到这一点?

1 个回答

3

根据我观察到的情况,实际上并没有内存泄漏。Python的内存管理代码(可能和你使用的操作系统的内存管理有关)决定保留原始字典(也就是没有合并数组的那个)的内存空间在程序中。不过,这部分内存是可以被重新使用的。我通过以下方式证明了这一点:

  1. 把你给出的代码做成一个函数,让它返回dataDict。
  2. 调用这个函数两次,把结果赋值给两个不同的变量。

当我这样做时,我发现内存使用量只从大约900 MB增加到大约1.3 GB。根据我的计算,如果没有额外的字典内存,Numpy数据本身应该大约占用427 MB,所以这个结果是合理的。我们函数创建的第二个初始未合并字典只是使用了已经分配的内存。

如果你真的不能使用超过大约600 MB的内存,我建议你对你的Numpy数组做一些类似于Python列表内部处理的事情:先分配一个有一定列数的数组,当这些用完后,再创建一个更大的数组,增加列数并把数据复制过去。这样可以减少合并的次数,速度会更快(虽然还是没有列表快),同时也能降低内存使用。当然,这样做实现起来会更麻烦一些。

撰写回答