[Python-Dev] optimizing non-local object access (original) (raw)

Skip Montanaro skip@pobox.com (Skip Montanaro)
Thu, 9 Aug 2001 11:28:36 -0500


I had a thought last night about optimizing access to globals in other modules (e.g. "math.sin" or "string.uppercase"). I think it could actually work to speed up all non-local object access.

The nice thing about module globals is that for the most part they are "almost constant". Once a module is imported, it's rare that its global name bindings actually change. I think it might suffice to create a notification system that allows an executing frame to register its interest in changes to a module's name bindings.

Let me make this idea concrete with an example. Suppose I have the following code:

import math

def sinseq(n):
    l = []
    for i in xrange(n):
        l.append(math.sin(i))
    return l

"math.sin" requires a global lookup and an attribute access each pass through the loop. Let's add an entry to the fastlocals array for it and an entry to the local names called "math.sin". The byte code compiler generates a LOAD_FAST instruction anywhere math.sin is accessed, and at the first point where it should be "live" (i.e., at the beginning of the function) it simply inserts a TRACK_OBJECT instruction with that slot in fastlocals as one argument and the "math.sin" slot in the local names as the other. When "math.sin" goes out of scope (i.e., at the end of the function), it inserts an UNTRACK_OBJECT instruction with "math.sin" as its argument. The same can be done for "l.append" and xrange. The bytecode for the above function would look something like:

        TRACK_OBJECT        math.sin
        TRACK_OBJECT        xrange
>>    0 BUILD_LIST          0 ('\000', '\000')
      3 STORE_FAST          1 (l)
        TRACK_OBJECT        l.append
      6 SETUP_LOOP         44 (to 53)
      9 LOAD_FAST           xrange
     12 LOAD_FAST           0 (n)
     15 CALL_FUNCTION       1 ('\001', '\000')
     18 LOAD_CONST          1 (0)
     21 FOR_LOOP           28 (to 52)
     24 STORE_FAST          2 (i)
     27 LOAD_FAST           l.append
        LOAD_FAST           math.sin
     39 LOAD_FAST           2 (i)
     42 CALL_FUNCTION       1 ('\001', '\000')
     45 CALL_FUNCTION       1 ('\001', '\000')
     48 POP_TOP        
     49 JUMP_ABSOLUTE      21 ('\025', '\000')
>>   52 POP_BLOCK      
>>   53 LOAD_FAST           1 (l)
        UNTRACK_OBJECT      l.append
        UNTRACK_OBJECT      xrange
        UNTRACK_OBJECT      math.sin
     56 RETURN_VALUE   

TRACK_OBJECT and UNTRACK_OBJECT are responsible for registering and unregistering interest in an object. TRACK_OBJECT also caches the corresponding object in the fastlocals array.

I think most or all of the work can be handled by PyObject_SetAttr and/or PyDict_SetItem. It seems to me that its greatest benefit would be to short circuit access to global variables in other modules. For the most part, these don't change once an import is completed, so you'd effectively be converting access to these objects into local variables, but you could use it to track all non-local variables or attributes.

stepping-behind-the-egg-proof-screen-ly y'rs,

Skip