While trying to write an answer for another SO question something really peculiar happened.
I basically came up with a one liner gcd and said it maybe slower because of recursion
gcd = lambda a,b : a if not b else gcd(b, a % b)
heres a simple test:
assert gcd(10, 3) == 1 and gcd(21, 7) == 7 and gcd(100, 1000) == 100
here are some benchmarks:
timeit.Timer('gcd(2**2048, 2**2048+123)', setup = 'from fractions import gcd').repeat(3, 100)
# [0.0022919178009033203, 0.0016410350799560547, 0.0016489028930664062]
timeit.Timer('gcd(2**2048, 2**2048+123)', setup = 'gcd = lambda a,b : a if not b else gcd(b, a % b)').repeat(3, 100)
# [0.0020480155944824219, 0.0016460418701171875, 0.0014090538024902344]
Well thats interesting I expected to be much slower but the timings are fairly close, ? maybe importing the module is the issue …
>>> setup = '''
... def gcd(a, b):
... """Calculate the Greatest Common Divisor of a and b.
...
... Unless b==0, the result will have the same sign as b (so that when
... b is divided by it, the result comes out positive).
... """
... while b:
... a, b = b, a%b
... return a
... '''
>>> timeit.Timer('gcd(2**2048, 2**2048+123)', setup = setup).repeat(3, 100)
[0.0015637874603271484, 0.0014810562133789062, 0.0014750957489013672]
nope still fairly close timings ok lets try larger values.
timeit.Timer('gcd(2**9048, 2**248212)', setup = 'gcd = lambda a,b : a if not b else gcd(b, a % b)').repeat(3, 100) [2.866894006729126, 2.8396279811859131, 2.8353509902954102]
[2.866894006729126, 2.8396279811859131, 2.8353509902954102]
timeit.Timer('gcd(2**9048, 2**248212)', setup = setup).repeat(3, 100)
[2.8533108234405518, 2.8411397933959961, 2.8430981636047363]
interesting I wonder whats going on?
I always assumed recursion was slower because of the overhead of calling a function, are lambdas the exception? and why I haven’t reach my recursion limit?
If implemented using def I hit it right away, if I increase the recursion depth to something like 10**9 I actually get a segmentation fault probably a stack overflow …
Update
>>> setup = '''
... import sys
... sys.setrecursionlimit(10**6)
...
... def gcd(a, b):
... return a if not b else gcd(b, a % b)
... '''
>>>
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = 'gcd = lambda a,b:a if not b else gcd(b, a%b)').repeat(3, 100)
[3.0647969245910645, 3.0081429481506348, 2.9654929637908936]
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = 'from fractions import gcd').repeat(3, 100)
[3.0753359794616699, 2.97499680519104, 3.0096950531005859]
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = setup).repeat(3, 100)
[3.0334799289703369, 2.9955930709838867, 2.9726388454437256]
>>>
even more puzzling …
The type of a lambda is exactly the same as the type of any other function, and in the case of both, if defined in another local scope, environment capture occurs.
The only difference is that functions defined with the lambda syntax do not automatically become the value of a variable in the scope in which it appears, and that lambda syntax requires the body to be one (possibly compound) expression, the value of which is returned from the function.
As to the speed of recursion – yes there’s a slight overhead, but apparently not that much. The call overhead would appear to mostly be made of the cost of allocating the stack frame.