为什么列表理解比乘法数组的numpy要快得多?

时间:2015-07-23 21:50:13

标签: python performance numpy list-comprehension matrix-multiplication

最近我回答了THIS问题想要2个列表的乘法,有些用户建议使用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中执行,我们使用了2个嵌套循环和zip函数那么原因是什么呢?是因为numpy中的操作*吗?

另请注意timeit此处没有问题我将import部分放入setup

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

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

1 个答案:

答案 0 :(得分:13)

创建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