为什么 Python 中无分支 max() 实现和内置 max() 较慢?

问题描述 投票:0回答:2

我发现了 2 个无分支函数,它们可以在 python 中查找两个数字的最大值,并将它们与 if 语句和内置 max 函数进行比较。我认为无分支或内置函数将是最快的,但最快的是 if 语句函数。有人知道这是为什么吗?功能如下:

If 语句(25000 次操作需要 2.16 秒):

def max1(a, b):
    if a > b:
        return a
    return b

内置(25000次操作4.69秒):

def max2(a, b):
    return max(a, b)

无分支 1(25000 次操作需要 4.12 秒):

def max3(a, b):
    return (a > b) * a + (a <= b) * b

无分支 2(25000 次操作需要 5.34 秒):

def max4(a, b):
    diff = a - b
    return a - (diff & diff >> 31)
python performance built-in branchless
2个回答
12
投票

您对分支与无分支代码的期望适用于汇编和 C 等低级语言。无分支代码在低级语言中可以更快,因为它可以防止因分支预测缺失而导致的速度减慢。 (注意:这意味着无分支代码可以更快,但不一定是。)

Python 是一种高级语言。假设您正在使用 CPython 解释器:对于您执行的每条字节码指令,解释器必须根据操作码类型进行分支,通常还需要根据许多其他内容进行分支。例如,即使是简单的

<
运算符也需要一个分支来检查
<
操作码,另一个分支来检查对象的类是否实现了
__lt__
方法,更多分支来检查右侧值是否为用于执行比较的有效类型,可能还有其他几个分支。由于这些原因,即使是所谓的“无分支”代码实际上也会导致大量分支。

因为Python是如此高级,所以与单个机器代码指令相比,每个字节码指令实际上做了相当多的工作。因此,像这样的简单代码的性能主要取决于必须解释多少字节码指令:

  • 您的
    max1
    函数必须执行三个局部变量加载、比较、条件跳转和返回。那是六个。
  • 您的
    max2
    函数会加载两次局部变量,一次加载全局变量(引用内置
    max
    ),并且还会进行函数调用;需要传递参数,并且与其他字节码指令相比相对昂贵。最重要的是,内置函数本身必须与您自己的
    max1
    函数执行相同的工作,所以难怪
    max2
    速度较慢。
  • 您的
    max3
    函数会加载六次局部变量、两次比较、两次乘法、一次加法和一次返回。这是 12 条指令,因此我们预计它需要大约两倍的时间
    max1
  • 同样,
    max4
    会加载五次局部变量,一次存储到局部变量,一次加载常量,两次减法,一次位移,一次按位“与”,一次返回。这又是十二条指令。

也就是说,请注意,如果我们直接将您的

max1
与内置函数
max
进行比较,而不是您的
max2
有额外的函数调用,您的
max1
函数仍然比 快一点内置的
max
。这可能是因为内置
max
接受可变数量的参数,这可能涉及构建参数元组,并且内置
max
函数还有一个分支来检查是否使用单个可迭代对象调用它争论(例如
max([3, 1, 4, 2])
),并以不同的方式处理这种情况;你的
max1
函数不会做这些事情。


3
投票

Python 代码未经过机器优化。在解释的代码中获得任何“无分支”代码优化的可能性极小。

无分支代码有时会更快,如果它有效地完成更少的工作,或者硬件因此能够做出更好的分支预测。

函数调用是有成本的,所以如果函数内部的代码过于琐碎,函数调用的成本就比较高。

缺少控制情况:只需在循环中调用内置 max 函数并进行比较(与 max2 中一样,但没有函数调用开销)。内置 max 很可能是用 C 实现的,并且它已经针对您的硬件进行了优化。

© www.soinside.com 2019 - 2024. All rights reserved.