Python 解释大型运行时变化

Python 解释大型运行时变化,python,python-3.x,numpy,Python,Python 3.x,Numpy,我对下面的分析结果感到有点困惑,希望听到一些解释,以便理解它们。我想我会将内积作为一个简单的函数来比较不同的可能实现: 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)) d

我对下面的分析结果感到有点困惑,希望听到一些解释,以便理解它们。我想我会将内积作为一个简单的函数来比较不同的可能实现:

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函数

为了分析这些函数,我使用以下方法:

import timeit

def showtime( fun, a, b, rep=500 ):
    def wrapped():
        return fun(a,b)
    t = timeit.Timer( wrapped )
    print( t.timeit(rep) )

场景1:Python列表 输出:

3.883254656990175
3.9970695309893927
2.5059548830031417
4.048957359002088
5.460344396007713
0.005460165994009003
因此,“更智能”的实现
dot2
实际上比幼稚的实现
dot1
性能差,而Numpy比两者都快得多。但是

场景2:Python数组 我想也许使用一个数字容器,比如,可以在引擎盖下实现某些优化

import array
a = array.array( 'd', a )
b = array.array( 'd', b )

showtime( dot1, a, b )
showtime( dot2, a, b )
showtime( dot3, a, b )
输出:

3.883254656990175
3.9970695309893927
2.5059548830031417
4.048957359002088
5.460344396007713
0.005460165994009003
没有。如果说有什么区别的话,那就是它让纯Python实现变得更糟,突出了“幼稚”和“智能”版本之间的区别,现在Numpy的速度提高了3个数量级


问题 问题1。我能理解这些结果的唯一方法是,如果Numpy在场景1中处理数据之前确实复制了数据,而在场景2中它只“指向”数据,这听起来合理吗

问题2。为什么我的“智能”实现系统性地比“幼稚”实现慢?如果我对Q1的预感是正确的,那么如果
sum
在引擎盖下做了一些聪明的事情,那么创建一个新数组完全有可能更快。是这样吗


第三季度。三个数量级!这怎么可能?我的实现是真的很愚蠢,还是有一些神奇的处理器指令来计算点积?

生成器/产量机制确实需要一些CPU周期。当您不想一次完成整个序列时,它为您节省的是内存,或者当您想交错几个相关的计算以降低序列中第一项的延迟时间时,它会有所帮助


在数组上使用
numpy
函数只允许它在连续的内存块上运行常规C代码,而不会从列表中的指针中取消引用
float
对象。因此,它变得异常快速(这是
numpy
的全部要点)。

生成器/屈服机制确实需要一些CPU周期。当您不想一次完成整个序列时,它为您节省的是内存,或者当您想交错几个相关的计算以降低序列中第一项的延迟时间时,它会有所帮助

在数组上使用
numpy
函数只允许它在连续的内存块上运行常规C代码,而不会从列表中的指针中取消引用
float
对象。因此它变得异常快速(这是
numpy
的全部要点)。

Q1 Python列表包含指向Python对象的指针,而数组直接包含这些数字。但是,底层numpy代码希望它是一个连续数组。因此,当传递一个列表时,它必须为列表中的每个元素将浮点值读入一个新数组

如评论中所述,使用numpy的内置阵列甚至更好

问题2 从生成器中获取值(从内存中)比python函数调用要便宜得多。这比列表理解要昂贵得多,在列表理解中,除了
x*y
之外的所有内容都在解释器内部处理。必须制作一份价格昂贵的清单,但这一成本似乎很快就被氨化了

第三季度 Numpy比magnitued快三个数量级,因为它构建在高度优化的低级库之上。根据使用的后端,它甚至可能使用多个线程。Python必须处理大量的开销,以使程序员在每一步都能更轻松地完成任务,因此这真的不是一场公平的竞赛

奖金 我提出了生成器的建议,因为通常构造列表的开销很大。然而,在这种情况下,这是没有意义的,因为列表上的sum()似乎比迭代器上的快。

Q1 Python列表包含指向Python对象的指针,而数组直接包含这些数字。但是,底层numpy代码希望它是一个连续数组。因此,当传递一个列表时,它必须为列表中的每个元素将浮点值读入一个新数组

如评论中所述,使用numpy的内置阵列甚至更好

问题2 从生成器中获取值(从内存中)比python函数调用要便宜得多。这比列表理解要昂贵得多,在列表理解中,除了
x*y
之外的所有内容都在解释器内部处理。必须制作一份价格昂贵的清单,但这一成本似乎很快就被氨化了

第三季度 Numpy比magnitued快三个数量级,因为它构建在高度优化的低级库之上。根据使用的后端,它甚至可能使用多个线程。Python必须处理大量的开销,以使程序员在每一步都能更轻松地完成任务,因此这真的不是一场公平的竞赛

奖金
我提出了生成器的建议,因为通常构造列表的开销很大。但是,在这种情况下,它是没有意义的,因为列表上的sum()似乎比迭代器上的快。

您可能可以通过删除列表理解周围的
[]
使版本1更快,从而将其转换为生成器instead@Azsgy它基本上变成了
dot2
;我刚刚测试了你说的,它给出了类似的运行时间。实际上,它的性能比使用
[]
:]时差有趣的是,就我的理解来说,这没有任何意义。它必须创建一个额外的列表,然后再看一看,你可能会使版本1更快