Python 可读性与阶乘递归效率

问题描述

当我注意到这一点时,我正在尝试用不同的方式在 python 中进行递归:

[为简洁而简化]

import time

def fac(n):
    if n in [0,1]:
        return 1
    else:
        return n * fac(n-1)


def fac2(n):
    total = 1
    if n == 0:
        return total
    else:
        return n * fac2(n-1)


t0 = time.time()
fac(997)
t1 = time.time()
print(t1-t0)

t3 = time.time()
fac2(997)
t4 = time.time()
print(t4-t3)

>>> 0.00124359130859375
>>> 0.00046324729919433594

我知道 time 实际上并不测量速度,但这些函数之间存在 [可以忽略不计] 整个数量级的差异。

我觉得 fac2() 只是看起来更漂亮,更容易阅读,但是,我应该避免使用它,因为声明一个新的 var 可能那么昂贵吗?

先谢谢你!


编辑: 感谢您的回复! (是的,我说 fac() 更快是错误的)我接受了回复中的建议并拨打了具体电话:

import time

def fac(n):
    if n in [0,1]:
        return 1
    else:
        return n * fac(n-1)


def fac2(n):
    total = 1
    if n == 0:
        return total
    else:
        return n * fac2(n-1)


def fac3(n):
    if n < 2:
        return 1
    else:
        return n * fac(n-1)


def fac4(N,m=0):
    if m == N:
        return max(1,N)
    h = (N+m)//2
    return fac4(h,m)*fac4(N,h+1)


print('fac1: ',end='')
t0 = time.time()
for i in range(1000):
    fac(997)
t1 = time.time()
print(t1-t0)

print('fac2: ',end='')
t3 = time.time()
for i in range(1000):
    fac2(997)
t4 = time.time()
print(t4-t3)

print('fac3: ',end='')
t5 = time.time()
for i in range(1000):
    fac3(997)
t6 = time.time()
print(t6-t5)

print('fac4: ',end='')
t7 = time.time()
for i in range(1000):
    fac4(997)
t8 = time.time()
print(t8-t7)

这大约是我多次运行程序时得到的平均值(WSL2 Ubuntu 18.04):

fac1: 0.4169285297393799
fac2: 0.46125292778015137
fac3: 0.36595988273620605
fac4: 0.3895738124847412
>>>
fac1: 0.42001867294311523
fac2: 0.454434871673584
fac3: 0.3698153495788574
fac4: 0.3903229236602783
>>>
fac1: 0.4169285297393799
fac2: 0.46125292778015137
fac3: 0.36595988273620605
fac4: 0.3895738124847412

现在,我非常很想知道为什么 fac3() 看起来是“最快”的。有人能解释一下原因吗?

再次感谢您。

解决方法

您的衡量方法具有误导性:

函数的单次执行并不能说明什么:分页、编译等的成本会对结果产生重大影响。当函数执行的次数较多时,可以减少这种一次性效应的影响。

按以下方式包装对不同 fac 函数的调用时:

t0 = time.time()
for i in range(10000):
    fac(997)
t1 = time.time()
print(t1-t0)

然后差异在 3% 的范围内 - 至少在我的系统上是这样。基于如此小的差异进行设计更改是不可取的 - 差异很容易是由于不在代码中的原因(跨越缓存页面边界等)。

结论:

  • 通常最好提高可读性
  • 在性能很重要的情况下,进行相应的测量和优化是可以的。但是,如您的示例所示,获得可靠且有意义的测量数据并非易事。
  • 不同解决方案之间的性能差异越小,您就越需要了解差异的真正原因并排除环境影响。
,

n<2 中使用 n in [0,1] 而不是 fac() 并避免在 fac2() 中使用不必要的变量(或至少给它一个适当的名称)会更好并仅在需要时分配)。

如果你要玩递归,你应该尝试绕过递归深度限制。那将更具挑战性。

例如:

def fact(N,m=0):
    if m==N: return max(1,N)
    h = (N+m)//2
    return fact(h,m)*fact(N,h+1)