[Python-Dev] Comparing closures and arguments (was Re: Scoping vs augmented assignment vs sets (Re: 'fast locals' in Python 2.5)

Josiah Carlson jcarlson at uci.edu
Wed Jun 14 22:00:41 CEST 2006


"Phillip J. Eby" <pje at telecommunity.com> wrote:
> At 11:26 AM 6/14/2006 -0700, Josiah Carlson wrote:
> >Ok, so here's a bit of a benchmark for you.
> >
> >     def helper(x,y):
> >         return y
> >
> >     def fcn1(x):
> >         _helper = helper
> >         y = x+1
> >         for i in xrange(x):
> >             y = _helper(x,y)
> >
> >     def fcn2(x):
> >         y = x+1
> >         def _helper(x):
> >             return y
> >         for i in xrange(x):
> >             y = _helper(x)
> >
> >
> >Can you guess which one is faster?  I guessed, but I was wrong ;).
> >
> > >>> x = 1000000
> > >>> min([fcn1(x) for i in xrange(10)]), min([fcn2(x) for i in xrange(10)])
> >(0.53200006484985352, 0.59299993515014648)
> >
> >It turns out that passing two arguments to a helper function is actually
> >faster than passing one argument and pulling a second out of an
> >enclosing scope.
> 
> That claim isn't necessarily supported by your benchmark, which includes 
> the time to *define* the nested function 10 times, but calls it only 45 
> times!  Try comparing fcn1(1000) and fcn2(1000) - I suspect the results 
> will be somewhat closer, but probably still in favor of fcn1.

Please re-read the code and test as I have specified.  You seem to have
misunderstood something in there, as in the example I provide, _helper
is called 1,000,000 times (and is defined only once for each call of
fcn2) during each fcn1 or fcn2 call, and _helper is only defined once
inside of fcn2.

Further, reducing the passes to 1000 preserves the relative performance
measures as I had previously stated.

    >>> x = 1000
    >>> min([fcn1(x) for i in xrange(10)]), min([fcn2(x) for i in xrange(10)])
    (0.00051907656835226135, 0.00056413073832572991)
    >>> x = 10000
    >>> min([fcn1(x) for i in xrange(10)]), min([fcn2(x) for i in xrange(10)])
    (0.0037536511925964078, 0.0044071910377851964)
    >>> x = 100000
    >>> min([fcn1(x) for i in xrange(10)]), min([fcn2(x) for i in xrange(10)])
    (0.053770416317590275, 0.057610581942384442)
    >>> x = 1000000
    >>> min([fcn1(x) for i in xrange(10)]), min([fcn2(x) for i in xrange(10)])
    (0.54333500712479577, 0.58664054298870383)


> However, I suspect that the remaining difference in the results would be 
> due to the fact that the interpreter loop has a "fast path" function call 
> implementation that doesn't work with closures IIRC.  Perhaps someone who's 
> curious might try adjusting the fast path to support closures, and see if 
> it can be made to speed them up without slowing down other "fast path" calls.

That would be an interesting direction for improving speed.

 - Josiah



More information about the Python-Dev mailing list