[Python-Dev] optimizing non-local object access

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.

<wave what="hands">

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.

</wave>

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

Skip