Calling a function is faster than not calling it?

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun May 10 23:58:15 EDT 2015


On Mon, 11 May 2015 07:08 am, BartC wrote:

> On 10/05/2015 10:58, Steven D'Aprano wrote:
>> from timeit import Timer
>>
>> def func():
>>      a = 2
>>      b = 3
>>      c = 4
>>      return (a+b)*(a-b)/(a*c + b*c)
>>
>>
>> code = func.__code__
>> assert func() == eval(code)
>>
>> t1 = Timer("eval; func()", setup="from __main__ import func")
>> t2 = Timer("eval(code)", setup="from __main__ import code")
>>
>> # Best of 10 trials.
>> print (min(t1.repeat(repeat=10)))
>> print (min(t2.repeat(repeat=10)))
> 
> Maybe the overheads of using eval() are significant when calling a
> simple function such as your example.
> 
> When I made it do a bit more work:
[...]
> Then the eval() call took only 3% longer rather than 100%.

Well duh :-)

If I use:

def func():
    time.sleep(365*24*60*60)  # Sleep for a year.


then the overhead of calling it will be immeasurably small, regardless of
how we call it. But I don't care about big expensive functions. I actually
care about the fastest way to execute a tiny piece of code, and for that,
the overhead of eval is significant.

But regardless of whether the overhead is 3% or 30%, there is an anomaly
here. Conceptually at least, calling a function does more work than
eval'ing the function's code object: the function evaluates the code
object, plus it has overhead of its own which eval'ing the code object
lacks.

Here is one possible, minimal, pseudo-code implementation of function
__call__:


def __call__(self, *args):
    ns = {}
    for i, parameter in enumerate(self.parameters):
        ns[parameter] = args[i]
    return eval(self.__code__, globals(), ns)

If I extract the call to eval, and run that alone, without needing to set up
function arguments, I should avoid the overhead of the __call__ method. We
should expect that:

    eval(self.__code__, globals(), ns)

should be faster than:

    do_something()
    eval(self.__code__, globals(), ns)

no matter how small and insignificant do_something() actually is. The
anomaly is that, according to my tests, and confirmed by others, this is
not the case. However function __call__ works, it doesn't use eval. So what
does it do?




-- 
Steven




More information about the Python-list mailing list