遍历大型npz文件的有效方法

时间:2019-03-14 16:11:13

标签: python arrays json file numpy

我有50000+个npz文件(文件大小为15MB),每个文件包含三个numpy数组A,B,C。

A是一个形状为numpy的数组(33000,); B是uint8类型的小数形状(33000,),C是小数形状(33000,224,224)。

npz_file_dict = np.load(npz_file_path)
num_records = len(npz_file_dict['A'])

A = npz_file_dict['A']
B = npz_file_dict['B']
C = npz_file_dict['C']


def get_items(num_records):
   for i in range(num_records)
      yield A[i], B[i], C[i]
for a,b,c in get_items(num_records):
   # do some work with (a,b,c)
   # store the result in a json format with {'a' : a, 'b' : b, 'c' : c}

for循环需要很长时间才能完成(有时350,000条记录需要花费超过35分钟的时间)。有没有一种有效的方法来解析记录?

编辑:我尝试做内存映射的numpy数组,但是由于它们仅对npy文件有效,因此它似乎无法提高性能。

编辑:需要对我提出的方法进行批评。

我一直使用内存映射来加快迭代速度,对于读取的每个文件,我都会花费一些时间(在我称为设置阶段)将这个庞大的numpy数组映射到文件指针。

num_records = len(npz_file_dict['np_cids'])
print(num_records)


start_memmap = time()
data = npz_file_dict['C']

print('Time to read data {}'.format(time() - start_memmap))


filename = path.join(mkdtemp(), 'newfile.dat')

print('Path to file',  filename)
fp = np.memmap(filename, dtype='float32', mode='w+', shape=(num_records, 224,224))
fp[:] = data[:]

finish_memmap = time()

print('Time to create memmap {}'.format(finish_memmap - start_memmap))

完成此设置后,我只需在for循环中遍历A和B数组,然后使用fp来获取相应的i^th记录。这种方法的优点是迭代速度非常快。缺点是建立时间介于单处理模式下的50秒和多处理模式下的100秒之间。

如果在30000条记录中摊销建立时间,则约为3.3毫秒。在迭代过程中,读取速度非常快-几百微秒。这样一来,每条记录所花费的总时间约为5毫秒。与我以前的方法(每条记录花费150ms)相比,这是96%的速度。

1 个答案:

答案 0 :(得分:1)

我认为您可以使用以下代码清理代码:

npz_file_dict = np.load(npz_file_path)
A = npz_file_dict['A']   # (33000,) shape array
B = npz_file_dict['B']
C = npz_file_dict['C']

for a,b,c in zip(A,B,C):
   # do some work with (a,b,c)
   # store the result in a json format with {'a' : a, 'b' : b, 'c' : c}

这清楚表明您正在使用从文件加载的阵列。我不知道它是否会加快速度;这取决于npz加载程序是否使用某种具有重复npz_file_dict['A]访问权限的缓存。

但是,无论如何创建或加载它们,通过numpy数组进行迭代的速度都很慢-比在其上使用编译的“整个数组”操作要慢得多。列表上的迭代速度更快。实际上,这样做可能会有所帮助

for a,b,c in zip(A.tolist(), B.tolist(), list(C):
    ...

A.tolist()是将数组转换为列表的快速方法。我不会在C上使用它,因为它是3d,并且我假设您想将C[i,:,:]用作数组,而不是嵌套列表。尽管由于您正在将c写到json,但您可能还是希望它是列表的列表。

===

memmap数组上进行迭代比在内存中数组上进行迭代要慢得多:

In [1]: C=np.ones((3000,224,224))                                               
In [2]: np.savez('bigC.npz',C=C)                                                
In [3]: fp = np.memmap('bigC.dat', dtype=C.dtype, mode='w+', shape=C.shape)     
In [4]: fp[:] = C[:]                                                            
In [5]: %%timeit  
   ...: for i in range(3000): 
   ...:     c = C[i] 
   ...:                                                                         
566 µs ± 2.55 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [7]: %%timeit  
   ...: for i in range(3000): 
   ...:     c = fp[i] 
   ...:      
   ...:                                                                         
9.74 ms ± 94.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

遍历npz负载确实很慢-我的测试仍在运行:

In [8]: %%timeit d = np.load('bigC.npz') 
   ...: for i in range(3000): 
   ...:     c = d['C'][i] 

以一个初始负载进行测试:

In [243]: d = np.load('bigC.npz')                                               
In [244]: %%timeit  
     ...: D = d['C'] 
     ...: for i in range(3000): 
     ...:     c = D[i] 
     ...:                                                                       
2.17 s ± 27.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [245]: %%timeit  
     ...: D = d['C'] 
     ...:  
     ...:                                                                       
2.14 s ± 6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

因此可以避免从npz一次加载大型数组,但是您当然不希望为每一行重复加载它。如果数组太大而不能容纳在内存中,则将其转储到内存映射中可能会有所帮助,这并不是对内存数组的改进。