为什么 Python 中的无分支函数和内置函数速度较慢?

9 python performance built-in branchless

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

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

def max1(a, b):
    if a > b:
        return a
    return b
Run Code Online (Sandbox Code Playgroud)

内置(25000次操作4.69秒):

def max2(a, b):
    return max(a, b)
Run Code Online (Sandbox Code Playgroud)

Branchless 1(25000 次操作需要 4.12 秒):

def max3(a, b):
    return (a > b) * a + (a <= b) * b
Run Code Online (Sandbox Code Playgroud)

Branchless 2(25000 次操作需要 5.34 秒):

def max4(a, b):
    diff = a - b
    return a - (diff & diff >> 31)
Run Code Online (Sandbox Code Playgroud)

kay*_*ya3 14

您对分支与无分支代码的期望适用于汇编和 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函数不做这些事情。