为什么列表推导在数组乘法方面比numpy快得多



最近我回答了这个问题,这个问题想要两个列表的乘法,一些用户建议使用numpy的以下方法,以及我的方法,我认为这是正确的方法:

(a.T*b).T

我还发现aray.resize()具有相同的性能。无论如何,另一个答案提出了使用列表推导的解决方案:

[[m*n for n in second] for m, second in zip(b,a)]

但是在基准测试之后,我看到列表理解执行得比numpy:

快得多。
from timeit import timeit
s1="""
a=[[2,3,5],[3,6,2],[1,3,2]]
b=[4,2,1]
[[m*n for n in second] for m, second in zip(b,a)]
"""
s2="""
a=np.array([[2,3,5],[3,6,2],[1,3,2]])
b=np.array([4,2,1])
(a.T*b).T
"""
print ' first: ' ,timeit(stmt=s1, number=1000000)
print 'second : ',timeit(stmt=s2, number=1000000,setup="import numpy as np")
结果:

 first:  1.49778485298
second :  7.43547797203

可以看到,numpy大约快了5倍。但最令人惊讶的是,它不使用转置的速度更快,对于以下代码:

a=np.array([[2,3,5],[3,6,2],[1,3,2]])
b=np.array([[4],[2],[1]])
a*b 

列表理解速度仍快5倍。因此,除了这一点,列表推导在C中执行这里我们使用了两个嵌套循环和一个zip函数,这是什么原因呢?是因为numpy的*手术吗?

还请注意,timeit在这里没有问题,我把import部分放在setup中。

我也尝试过更大的数组,差异变小,但仍然没有意义:

s1="""
a=[[2,3,5],[3,6,2],[1,3,2]]*10000
b=[4,2,1]*10000
[[m*n for n in second] for m, second in zip(b,a)]
"""
s2="""
a=np.array([[2,3,5],[3,6,2],[1,3,2]]*10000)
b=np.array([4,2,1]*10000)
(a.T*b).T
"""

print ' first: ' ,timeit(stmt=s1, number=1000)
print 'second : ',timeit(stmt=s2, number=1000,setup="import numpy as np")
结果:

 first:  10.7480301857
second :  13.1278889179

创建numpy数组比创建列表慢得多:

In [153]: %timeit a = [[2,3,5],[3,6,2],[1,3,2]]
1000000 loops, best of 3: 308 ns per loop
In [154]: %timeit a = np.array([[2,3,5],[3,6,2],[1,3,2]])
100000 loops, best of 3: 2.27 µs per loop

还可以在肉之前调用NumPy函数所产生的固定成本计算的一部分可以通过一个快速的底层C/Fortran函数来完成。这可以包括确保输入是NumPy数组,

这些设置/固定成本是在假设NumPy之前要记住的解决方案本质上比纯python解决方案更快。NumPy发光的时候设置大型数组一次,然后执行许多快速NumPy操作在阵列上。如果数组很小,它可能无法胜过纯Python因为设置成本可能会超过卸载计算的好处编译C/Fortran函数。对于小型数组来说,这可能还不够计算使它值得。


如果您将数组的大小增加一点,并移动数组的创建,那么NumPy可以比纯Python快得多:

import numpy as np
from timeit import timeit
N, M = 300, 300
a = np.random.randint(100, size=(N,M))
b = np.random.randint(100, size=(N,))
a2 = a.tolist()
b2 = b.tolist()
s1="""
[[m*n for n in second] for m, second in zip(b2,a2)]
"""
s2 = """
(a.T*b).T
"""
s3 = """
a*b[:,None]
"""
assert np.allclose([[m*n for n in second] for m, second in zip(b2,a2)], (a.T*b).T)
assert np.allclose([[m*n for n in second] for m, second in zip(b2,a2)], a*b[:,None])
print 's1: {:.4f}'.format(
    timeit(stmt=s1, number=10**3, setup='from __main__ import a2,b2'))
print 's2: {:.4f}'.format(
    timeit(stmt=s2, number=10**3, setup='from __main__ import a,b'))
print 's3: {:.4f}'.format(
    timeit(stmt=s3, number=10**3, setup='from __main__ import a,b'))
收益率

s1: 4.6990
s2: 0.1224
s3: 0.1234

最新更新