Bizarre additional calling overhead.

Chris Mellon arkanes at gmail.com
Sat Nov 3 15:47:56 EDT 2007


On Sat, 2007-11-03 at 01:08 -0300, Gabriel Genellina wrote:
> En Fri, 02 Nov 2007 21:07:19 -0300, Matimus <mccredie at gmail.com> escribió:
> 
> > On Nov 2, 3:08 pm, "Chris Mellon" <arka... at gmail.com> wrote:
> >> >>> def test_func():
> >>
> >> ...     pass
> >> ...>>> import new
> >> >>> test_func2 = new.function(test_func.func_code, {}, "test_func2")
> >> >>> test_func2
> >>
> >> <function test_func2 at 0x01B8C2F0>>>> test_func
> >>
> >> <function test_func at 0x01B8C270>>>> import timeit
> >> >>> tf = timeit.Timer("test_func()", "from __main__ import test_func")
> >> >>> tf.repeat()
> >>
> >> [0.2183461704377247, 0.18068215314489791, 0.17978585841498085]>>> tf2 =  
> >> timeit.Timer("test_func2()", "from __main__ import test_func2")
> >> >>> tf2.repeat()
> >>
> >> [0.40015390239890891, 0.35893452879396648, 0.36034628133737456]
> >>
> >> Why almost twice the calling overhead for a dynamic function?
> >
> > So, I don't have an official explanation for why it takes twice as
> > long, but the only difference between the two functions I could find
> > was that test_func.func_globals was set to globals() and
> > test_func2.func_globals was an empty dict. When I re-created
> > test_func2 with globals set to globals() it ran just as fast as
> > test_func.
> 
> Yes - and that's a very important difference. Not because it's empty, nor  
> because it's not the same as globals(), but because the builtins as seen  
> by the function are not from the original __builtin__ module.
> 
>  From the Python Reference Manual, section 4.1:
> 	The built-in namespace associated with the execution of a code block is  
> actually found by looking up the name __builtins__ in its global  
> namespace; [...] __builtins__ can be set to a user-created dictionary to  
> create a weak form of restricted execution.
> 
>  From the Library Reference, section 28, Restricted Execution:
> 	The Python run-time determines whether a particular code block is  
> executing in restricted execution mode based on the identity of the  
> __builtins__ object in its global variables: if this is (the dictionary  
> of) the standard __builtin__ module, the code is deemed to be  
> unrestricted, else it is deemed to be restricted.
> 
> Section 3.2, when describing frame objects:
> 	f_restricted is a flag indicating whether the function is executing in  
> restricted execution mode
> 
> Let's try to see if this is the case. Getting the f_restricted flag is a  
> bit hard with an empty globals(), so let's pass sys as an argument:
> 
> py> def test_func(sys):
> ...     print "restricted", sys._getframe().f_restricted
> ...
> py> import sys, new
> py> test_func2 = new.function(test_func.func_code, {}, "test_fun
> c2")
> py> test_func(sys)
> restricted False
> py> test_func2(sys)
> restricted True
> 
> So test_func2 is running in restricted mode. That't the reason it is so  
> slow. Even if the restricted mode implementation is now considered unsafe  
> -because new style classes provide many holes to "escape" from the "jail"-  
> the checks are still being done.
> 
> Ok, now let's try to avoid entering restricted mode:
> 
> py> import __builtin__
> py> test_func3 = new.function(test_func.func_code, {'__builtins_
> _':__builtin__}, "test_func3")
> py> test_func3(sys)
> restricted False
> 
> And now, repeating the timings when __builtins__ is correctly set to the  
> builtin module __builtin__ (!), shows no difference with the original  
> function. So this was the cause of the slow down.
> 
> The rule is: unless you *actually* want to execute code in restricted  
> mode, pass globals() when building a new function object, or at least, set  
> '__builtins__' to the __builtin__ module

Thanks for the very informative and detailed information. After posting
I, like the other responders, figured out that it was related to the
empty globals dict but I had no idea how that could be happening. 




More information about the Python-list mailing list