Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python “functools.partial”的优化功能究竟是什么?_Python_Performance_Cpython_Python Internals_Functools - Fatal编程技术网

Python “functools.partial”的优化功能究竟是什么?

Python “functools.partial”的优化功能究竟是什么?,python,performance,cpython,python-internals,functools,Python,Performance,Cpython,Python Internals,Functools,CPython 3.6.4: from functools import partial def add(x, y, z, a): return x + y + z + a list_of_as = list(range(10000)) def max1(): return max(list_of_as , key=lambda a: add(10, 20, 30, a)) def max2(): return max(list_of_as , key=parti

CPython 3.6.4:

from functools import partial

def add(x, y, z, a):
    return x + y + z + a

list_of_as = list(range(10000))

def max1():
    return max(list_of_as , key=lambda a: add(10, 20, 30, a))

def max2():
    return max(list_of_as , key=partial(add, 10, 20, 30))
现在:

我认为
partial
只记住部分参数,然后在使用其余参数调用时将它们转发到原始函数(因此这只是一个快捷方式),但它似乎进行了一些优化。在我的例子中,整个
max2
函数比
max1
优化了15%,这非常好

知道优化是什么会很好,这样我就可以更有效地使用它。对于任何优化都保持沉默。毫不奇怪,“大致相当于”实施(在文档中给出)根本没有优化:

In [3]: %timeit max2()  # using `partial` implementation from docs 
10.7 ms ± 267 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

以下参数实际上只适用于CPython,对于其他Python实现,可能会完全不同。您实际上说过您的问题是关于CPython的,但是我认为重要的是要认识到,这些深入的问题几乎总是取决于实现细节,对于不同的实现,这些细节可能不同,甚至在不同的CPython版本之间也可能不同(例如,CPython 2.7可能完全不同,但CPython 3.5也可能完全不同)

时间安排 首先,我无法重现15%甚至20%的差异。在我的计算机上,差异约为10%。当您更改
lambda
时,差异甚至更小,因此它不必从全局范围查找
add
(正如注释中已经指出的,您可以将
add
函数作为默认参数传递给函数,以便在本地范围内进行查找)

实际上,我对这些进行了基准测试:

可能的解释 很难找到造成这种差异的确切原因。不过,假设您有一个(我使用的所有桌面版本的CPython都有它),有一些可能的选择

正如您已经发现的,速度将显著减慢

  • partial
    是用C实现的,可以直接调用函数,而不需要中间的Python层1。另一方面,
    lambda
    需要在Python级别调用“captured”函数

  • partial
    实际上知道参数是如何组合在一起的。因此,它可以更有效地创建传递给函数的参数,而不是构建一个全新的参数元组

  • 在最近的Python版本中,为了优化函数调用(所谓的FASTCALL优化),对一些内部构件进行了更改。Victor Stiner的页面上有一个相关pull请求的列表,以备您进一步了解

    这可能会影响
    lambda
    partial
    ,但同样,因为
    partial
    是一个C函数,它知道直接调用哪个函数,而不必像
    lambda
    那样推断它

但是,必须认识到创建
部分
有一些开销。盈亏平衡点是针对约10个列表元素,如果列表较短,则
lambda
将更快

脚注 1如果从Python调用函数,它将使用OP code
call_函数
,这实际上是一个。但它还包括创建参数元组/字典。如果从C函数调用函数,则可以通过直接调用
PyObject_call*
函数来避免这种薄型包装

如果您对操作码感兴趣,您可以:

导入dis
dis.dis(最大λ默认值)
0加载\u全局0(最大值)
2快速加载0(lst)
4加载_全局1(添加)
6构建元组1
8加载常数1()
10加载常数2(“最大λ默认值”)
12 MAKE_功能1(默认值)
14荷载常数3((‘键’,))
16呼叫功能功率2千瓦
18返回值
反汇编
0 LOAD_FAST 1(add)不知道partial,谢谢你提醒我。作为一个冒昧的猜测:partial只需要存储一个变量,而不是全部四个变量,因此创建新堆栈帧的开销较小,但这只是一个模糊的(很可能是错误的)猜猜看。我正在等待专业人士深入解释:)如果使用“def”而不是lambda,有什么区别吗?@MichalCharemza我刚刚检查过(创建了一个外部
def助手(a):返回add(10,20,30,a)
,并在
max
中使用它)速度没有差别。
partial
也有C源代码:一个区别是
max1
需要在全局范围内为每个调用查找
add
,而
max2
只查找一次。将
add=add
添加为lambda参数将使其更快。或者,您可以将其置于n一个局部变量
max1
。不是
functools.partial
从Python 3.4开始就在Python中实现了?@PeterNimroot这只是一个后备方法,以防您没有编译的
\u functools
模块()。而且很少没有。@mseifer小精度wrt名称解析:
LOAD\u name
是因为
compile
不知道这里的作用域是什么,如果你
dis.dis(lambda:add(10,20,30,a)
)您会发现,函数体使用专门的
LOAD\u GLOBAL
LOAD\u FAST
而不是更通用的
LOAD\u NAME
LOAD\u FAST
可以直接索引到帧的本地名称空间,而不必查找和解析名称,从而使其速度特别快,add=add:add(10,20,30,a)
这个
add
也是
LOAD\u FAST
-ed,这就是为什么这个表单明显更快(这是一个常见的优化)。@masklin这就是我的意思。
LOAD\u NAME
使它变慢了-如果是的话会发生什么
In [3]: %timeit max2()  # using `partial` implementation from docs 
10.7 ms ± 267 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
from functools import partial

def add(x, y, z, a):
    return x + y + z + a

def max_lambda_default(lst):
    return max(lst , key=lambda a, add=add: add(10, 20, 30, a))

def max_lambda(lst):
    return max(lst , key=lambda a: add(10, 20, 30, a))

def max_partial(lst):
    return max(lst , key=partial(add, 10, 20, 30))
from simple_benchmark import benchmark
from collections import OrderedDict

arguments = OrderedDict((2**i, list(range(2**i))) for i in range(1, 20))
b = benchmark([max_lambda_default, max_lambda, max_partial], arguments, "list size")

%matplotlib notebook
b.plot_difference_percentage(relative_to=max_partial)