我对以下分析结果感到有些困惑,想听听一些解释以便理解它们。我想我会把内积作为一个简单的函数来比较不同的可能实现:
import numpy as np
def iterprod(a,b):
for x,y in zip(a,b):
yield x*y
def dot1(a,b):
return sum([ x*y for x,y in zip(a,b) ])
def dot2(a,b):
return sum(iterprod(a,b))
def dot3(a,b):
return np.dot(a,b)
第一个实现 dot1 是一个“朴素”的实现,我们首先创建一个新的成对产品列表,然后对其元素求和。我认为第二个实现 dot2 会更聪明一些,因为它消除了创建新列表的需要。第三个实现 dot3 使用 Numpy 的 dot功能。
为了分析这些功能,我使用了以下内容:
import timeit
def showtime( fun, a, b, rep=500 ):
def wrapped():
return fun(a,b)
t = timeit.Timer( wrapped )
print( t.timeit(rep) )
场景一:Python 列表
import random
n = 100000
a = [ random.random() for k in range(n) ]
b = [ random.random() for k in range(n) ]
showtime( dot1, a, b )
showtime( dot2, a, b )
showtime( dot3, a, b )
输出:
3.883254656990175
3.9970695309893927
2.5059548830031417
所以“更聪明”的实现 dot2 实际上比原始的 dot1 执行得更差,而 Numpy 比两者都快得多。但是然后..
场景二:Python数组
我想也许可以使用数字容器,比如 array ,也许会在引擎盖下启用某些优化。
import array
a = array.array( 'd', a )
b = array.array( 'd', b )
showtime( dot1, a, b )
showtime( dot2, a, b )
showtime( dot3, a, b )
输出:
4.048957359002088
5.460344396007713
0.005460165994009003
没有。如果有的话,它使纯 Python 实现的事情变得更糟,突出了“朴素”和“智能”版本之间的差异,现在 Numpy 是 3 个数量级更快!
问题
Q1。我能理解这些结果的唯一方法是,如果 Numpy 在场景 1 中处理数据之前实际复制了数据,而在场景 2 中它只是“指向”它,这听起来合理吗?
Q2。为什么我的“智能”实现在系统上比“天真”实现慢?如果我对 Q1 的预感是正确的,那么如果 sum 在幕后做了一些聪明的事情,那么创建一个新数组完全有可能更快。是吗?
Q3。 3个数量级!这怎么可能?我的实现真的很愚蠢,还是有一些神奇的处理器指令来计算点积?
请您参考如下方法:
Q1
Python 列表包含指向 Python 对象的指针,而数组直接包含这些数字。然而,底层的 numpy 代码期望它是一个连续的数组。因此,当传递一个列表时,它必须将 float 中的值读取到列表中每个元素的新数组中。
如评论中所述,使用 numpy 的内置数组会更好。
Q2
从生成器中获取值(从内存中)比 python 函数调用的开销要小一些。这比列表理解要昂贵得多,在列表理解中,除了 x*y 之外的所有内容都在解释器内部处理。必须制作一份 list ,这很昂贵,但这笔费用似乎可以很快摊销。
Q3
Numpy 的速度提高了三个数量级,因为它建立在高度优化的低级库之上。根据使用的后端,它甚至可能使用多个线程。 Python 必须处理大量开销才能让程序员在开发过程中的每一步都变得更轻松,因此这确实是一场不公平的比赛。
奖金
我提出了我的生成器建议,因为通常构建列表的开销很大。然而,在这种情况下,它没有实际意义,因为列表上的 sum() 似乎比迭代器上的更快。
