[Python-Dev] Proposed PEP: Optimizing Global Variable and Attribute Access

Skip Montanaro skip at pobox.com
Tue Aug 14 08:37:14 EDT 2001


    Roman> What about threads? What if math.sin changes while in cache?

I added the following to a new Questions section:

Q.  What about threads?  What if math.sin changes while in cache?

A.  I believe the global interpreter lock will protect values from being
    corrupted.  In any case, the situation would be no worse than it is
    today.  If one thread modified math.sin after another thread had
    already executed "LOAD_GLOBAL math", but before it executed
    "LOAD_ATTR sin", the client thread would see the old value of
    math.sin. 

    The idea is this.  I will use a multi-attribute load as an example,
    not because it would happen very often, but because by demonstrating
    the recursive nature with an extra call hopefully it will become
    clearer what I have in mind.  Suppose a function defined in module
    foo wants to access spam.eggs.ham and that spam is a module imported
    at the module level in foo:

        import spam
        ...
        def somefunc():
            ...
            x = spam.eggs.ham

    Upon entry to somefunc, a TRACK_GLOBAL instruction will be executed:

        TRACK_GLOBAL spam.eggs.ham &4

    "spam.eggs.ham" is a string literal stored in the function's
    constants array. "&4" is a reference to a slot in the executing
    frame's fast locals array.  Here's what I envision happening:

    1. The TRACK_GLOBAL instruction locates the object referred to by
       the name "spam" and finds it in its global scope.  It then
       executes a C function like

           _PyObject_TrackName(m, "spam.eggs.ham", &4)

       where "m" is the module object

    2. The module object strips the leading "spam." stores the necessary
       information ("eggs.ham" and &4) in case its binding for the name
       "eggs" changes.  It then locates the next name in its dict, in
       this case "eggs", and recursively calls

           _PyObject_TrackName(eggs, "eggs.ham", &4)

    3. The eggs object strips the leading "eggs.", stores the ("ham",
       &4) info, locates the object in its namespace called "ham" and
       calls _PyObject_TrackName once again:

           _PyObject_TrackName(ham, "ham", &4)

    4. The "ham" object strips the leading string (no "." this time, but
       that's a minor point), sees that the result is empty, then uses
       its own value (self, probably) to update the location it was
       handed:

           Py_XDECREF(&4);
           &4 = self;
           Py_INCREF(&4);

    Now, at this point, each object involved in resolving
    "spam.eggs.ham" knows which entry in its namespace needs to be
    tracked and what location to update if that name changes.
    Furthermore, if the one name it is tracking in its local storage
    changes, it can call _PyObject_TrackName using the new object once
    the change has been made.  At the bottom end of the food chain, the
    last object will always strip a name, see the empty string and know
    that its value should be stuffed into the location it's been passed.

    When the object referred to by the dotted expression "spam.eggs.ham"
    is going to go out of scope, "UNTRACK_GLOBAL spam.eggs.ham &4" is
    executed.  It has the effect of causing each object in the chain to
    delete its tracking information and recursively call its subsidiary
    object to do the same.

    The tracking operation may seem expensive, but recall that the
    objects being tracked are assumed to be "almost constant", so the
    setup cost will be traded off against hopefully multiple local
    instead of global loads.  For globals with attributes the tracking
    setup cost grows but is offset by avoiding the extra LOAD_ATTR cost.
    The TRACK_GLOBAL instruction needs to perform a PyDict_GetItemString
    for the first name in the chain to determine where the top-level
    object resides.  Each object in the chain has to store a string and
    an address somewhere, probably in a dict that uses storage locations
    as keys (e.g. the &4) and strings as values.  (This dict could
    possibly be a central dict of dicts whose keys are object addresses
    instead of a per-object dict.)  It shouldn't be the other way around
    because multiple active frames may want to track "spam.eggs.ham",
    but only one frame will want to associate that name with one of its
    fast locals slots.

Let me know if that doesn't address your concerns.

Skip




More information about the Python-list mailing list