加速结构化 NumPy 数组

11

NumPy的数组在性能和易用性方面都很出色(比列表更容易进行切片和索引)。

我试图使用NumPy结构化数组构造数据容器,而不是NumPy数组dict。问题是性能要差得多。对于同质数据,性能差约2.5倍,对于异质数据(我说的是NumPy数据类型),性能差约32倍。

有没有办法提高结构化数组的速度?我尝试将内存顺序从“c”更改为“f”,但这没有任何影响。

这是我的分析代码:

import time
import numpy as np

NP_SIZE = 100000
N_REP = 100

np_homo = np.zeros(NP_SIZE, dtype=[('a', np.double), ('b', np.double)], order='c')
np_hetro = np.zeros(NP_SIZE, dtype=[('a', np.double), ('b', np.int32)], order='c')
dict_homo = {'a': np.zeros(NP_SIZE), 'b': np.zeros(NP_SIZE)}
dict_hetro = {'a': np.zeros(NP_SIZE), 'b': np.zeros(NP_SIZE, np.int32)}

t0 = time.time()
for i in range(N_REP):
    np_homo['a'] += i

t1 = time.time()
for i in range(N_REP):
    np_hetro['a'] += i

t2 = time.time()
for i in range(N_REP):
    dict_homo['a'] += i

t3 = time.time()
for i in range(N_REP):
    dict_hetro['a'] += i
t4 = time.time()

print('Homogeneous Numpy struct array took {:.4f}s'.format(t1 - t0))
print('Hetoregeneous Numpy struct array took {:.4f}s'.format(t2 - t1))
print('Homogeneous Dict of numpy arrays took {:.4f}s'.format(t3 - t2))
print('Hetoregeneous Dict of numpy arrays took {:.4f}s'.format(t4 - t3))

编辑:忘记了放上我的计时数字:

Homogenious Numpy struct array took 0.0101s
Hetoregenious Numpy struct array took 0.1367s
Homogenious Dict of numpy arrays took 0.0042s
Hetoregenious Dict of numpy arrays took 0.0042s

编辑2:我使用了timit模块添加了一些额外的测试用例:

import numpy as np
import timeit

NP_SIZE = 1000000

def time(data, txt, n_rep=1000):
    def intern():
        data['a'] += 1

    time = timeit.timeit(intern, number=n_rep)
    print('{} {:.4f}'.format(txt, time))


np_homo = np.zeros(NP_SIZE, dtype=[('a', np.double), ('b', np.double)], order='c')
np_hetro = np.zeros(NP_SIZE, dtype=[('a', np.double), ('b', np.int32)], order='c')
dict_homo = {'a': np.zeros(NP_SIZE), 'b': np.zeros(NP_SIZE)}
dict_hetro = {'a': np.zeros(NP_SIZE), 'b': np.zeros(NP_SIZE, np.int32)}

time(np_homo, 'Homogeneous Numpy struct array')
time(np_hetro, 'Hetoregeneous Numpy struct array')
time(dict_homo, 'Homogeneous Dict of numpy arrays')
time(dict_hetro, 'Hetoregeneous Dict of numpy arrays')

导致结果为:

Homogeneous Numpy struct array 0.7989
Hetoregeneous Numpy struct array 13.5253
Homogeneous Dict of numpy arrays 0.3750
Hetoregeneous Dict of numpy arrays 0.3744

两种方法得到的运行时间比例相对稳定,在使用不同大小的数组时依然如此。

如果有需要说明的话: Python版本:3.4 NumPy版本:1.9.2


2
由于这个问题询问了NumPy的一个具体性能问题而不是一般性的评论,因此它已经从Code Review迁移到Stack Overflow。 - 200_success
如果你真的想要使用结构化数组,我建议尝试一下pandas - Imanol Luengo
1
请查看此问题:https://github.com/numpy/numpy/issues/6467 - MaxNoe
我在这里看到相同的时间。至于np_homonp_hetero,可能与对齐有关,因为np.int64作为第二个dtype并不那么慢。 - user2379410
@MaxNoe。在我提出这个问题之前,我已经看到过它。然而,我相信这不是同样的问题,因为我使用的是1.9.2版本,而这个问题是在1.10中新出现的。 - magu_
问题6467是对不同的numpy版本进行比较,而不是NumPy结构化数组和Python字典之间的比较。我这里也有类似的时间记录。但是还没有令人满意的答案。 - dojuba
1个回答

3

在我的快速定时测试中,差别并不是很大:

In [717]: dict_homo = {'a': np.zeros(10000), 'b': np.zeros(10000)}
In [718]: timeit dict_homo['a']+=1
10000 loops, best of 3: 25.9 µs per loop
In [719]: np_homo = np.zeros(10000, dtype=[('a', np.double), ('b', np.double)])
In [720]: timeit np_homo['a'] += 1
10000 loops, best of 3: 29.3 µs per loop

dict_homo 的情况下,数组嵌入到字典中是一个次要的点。像这样简单的字典访问非常快,基本上与通过变量名称访问数组相同。
因此,第一种情况基本上是对 1d 数组的 += 的测试。
在结构化的情况下,ab 值在数据缓冲区中交替出现,因此 np_homo['a'] 是一个视图,'提取'了替代的数字。所以它会稍微慢一些并不令人惊讶。
In [721]: np_homo
Out[721]: 
array([(41111.0, 0.0), (41111.0, 0.0), (41111.0, 0.0), ..., (41111.0, 0.0),
       (41111.0, 0.0), (41111.0, 0.0)], 
      dtype=[('a', '<f8'), ('b', '<f8')])

一个二维数组也会交错列值。
In [722]: np_twod=np.zeros((10000,2), np.double)
In [723]: timeit np_twod[:,0]+=1
10000 loops, best of 3: 36.8 µs per loop

令人惊讶的是,它实际上比结构化情况要慢一些。使用 order='F' 或 (2,10000) 形状会略微提高速度,但仍不如结构化情况好。

这些都是小型测试时间,所以我不会做出过大的声明。但是结构化数组不会回溯。


另一个时间测试是,每次都初始化数组或字典。

In [730]: %%timeit np.twod=np.zeros((10000,2), np.double)
np.twod[:,0] += 1
   .....: 
10000 loops, best of 3: 36.7 µs per loop
In [731]: %%timeit np_homo = np.zeros(10000, dtype=[('a', np.double), ('b', np.double)])
np_homo['a'] += 1
   .....: 
10000 loops, best of 3: 38.3 µs per loop
In [732]: %%timeit dict_homo = {'a': np.zeros(10000), 'b': np.zeros(10000)}
dict_homo['a'] += 1
   .....: 
10000 loops, best of 3: 25.4 µs per loop

在性能方面,2D和结构化数组更加接近,对于字典(1D)情况略有提升。我也试过使用np.ones,因为np.zeros可能存在延迟分配,但是行为没有任何区别。


嗯,这很有趣。尤其是前面的结果。你试过增加元素的大小吗?只是为了确定所需时间没有被某些常数主导。 - magu_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接