How can I speed up python function calls. I know I can use pypy to speed it up by a lot, but would like to see how fast I can get by sticking to regular python interpreter.
Creating functions per tasks makes code cleaner and easier to read, but seems to slow down the code by a lot, especially if these functions are being called millions of times.
Take a simple example below. Of course in reality these functions are more complicated, but I just used a simple function for each just to show how much additional overhead a function call can have. The first example uses functions, where get_type
is called 50 million times, do_even
is called 25 million times, and do_odd
is called 25 million times. The second example removed all these function calls, and placed the logic directly. This looks ok for this example, but for real life examples with more complicated logic and to have clean code, this may not be achievable. The third example utilizes generators, which is a bit faster than using functions, but not as fast as not using functions.
import datetime
TOTAL = 50000000
def timer(func):
def wrapper(*args, **kwargs):
start = datetime.datetime.now()
res = func(*args, **kwargs)
print("{} took: {}".format(func.__name__, datetime.datetime.now() - start))
return res
return wrapper
def get_type(num):
if num%2:
return "even"
else:
return "odd"
def do_even(counter):
counter["even"] += 1
def do_odd(counter):
counter["odd"] += 1
@timer
def with_functions():
counter = {"even": 0, "odd": 0}
for num in range(TOTAL):
type = get_type(num)
if type == "even":
do_even(counter)
else:
do_odd(counter)
@timer
def no_functions():
counter = {"even": 0, "odd": 0}
type = None
for num in range(TOTAL):
if num % 2:
type = "even"
else:
type = "odd"
if type == "even":
counter["even"] += 1
else:
counter["odd"] += 1
def g_evens(counter):
while 1:
counter["even"] += 1
yield
def g_odds(counter):
while 1:
counter["odd"] += 1
yield
@timer
def generators():
counter = {"even": 0, "odd": 0}
g_even = g_evens(counter)
g_odd = g_odds(counter)
for num in range(TOTAL):
if num % 2:
next(g_even)
else:
next(g_odd)
with_functions()
no_functions()
generators()
Output in python 3 (linux):
with_functions took: 0:00:18.344544
no_functions took: 0:00:09.707193
generators took: 0:00:12.016994
Output in pypy3 (linux):
with_functions took: 0:00:01.562282
no_functions took: 0:00:01.513855
generators took: 0:00:02.496888
In pypy3, with functions and no functions does not seem to have much of a difference. Is there a way to do this in regular python interpreter? Or should I not really worry about it, since in reality the more complicated logic can take longer and the 9 seconds extra is really nothing. But here since the example is so simple and quick, the 9 seconds seems a lot (~50%) of the total execution..