简体   繁体   中英

Python readabiity vs efficiency in factorial recursion

I'm playing around with different ways of doing recursion in python when I noticed this:

[Simplified for bevity]

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

I know that time doesn't actually measure speed but there is an [arguably negligible] whole order of magnitude of difference between these functions.

I feel that fac2() just looks prettier and is easier to read but, should I avoid it because declaring a new var can be that expensive?

Thank you in advance!


EDIT: Thanks for the replies! (yes I made an error in saying fac() is faster) I took the advice from the responses and dialed up the specificity:

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)

This is about the average I get when I run the program a bunch of times (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

Now, I'm very curious to know why fac3() appears to be the "fastest" of the bunch. Could someone explain why?

Thank you kindly, again, in advance.

Your measurement approach is misleading:

A single execution of a function is not indicating much: Costs for paging, compilation etc. can have a significant impact on the results. When executing the functions a larger number of times, the impact of such one-time effects can be reduced.

When wrapping the calls to the different fac functions in the following way:

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

Then differences are in the range of 3% - at least on my system. Design changes based on such small differences are not advisable - the differences can easily be due to reasons that do not lie within the code (crossing cache page boundaries etc.).

Conclusion:

  • It is usually best to go for readability
  • Where performance matters, doing measurements and optimizing accordingly is OK. However, as your example shows, coming to reliable and meaningful measurement data is not trivial.
  • The smaller the performance differences between the different solutions the better you have to understand the true reasons for the difference and exclude impacts of the environment.

What would be prettier would be to use n<2 instead of n in [0,1] in fac() and to avoid the unnecessary variable in fac2() (or at least give it an appropriate name and assign it only when needed).

If you're gonna play with recursion, you should try to circumvent the recursion depth limitation. That would be more challenging.

For example:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM