[Python-checkins] CVS: python/nondist/peps pep-0280.txt,1.5,1.6
Tim Peters
tim_one@users.sourceforge.net
Sun, 10 Feb 2002 23:05:03 -0800
Update of /cvsroot/python/python/nondist/peps
In directory usw-pr-cvs1:/tmp/cvs-serv22472
Modified Files:
pep-0280.txt
Log Message:
Flesh out a more aggressive builtin-caching variation.
Index: pep-0280.txt
===================================================================
RCS file: /cvsroot/python/python/nondist/peps/pep-0280.txt,v
retrieving revision 1.5
retrieving revision 1.6
diff -C2 -d -r1.5 -r1.6
*** pep-0280.txt 11 Feb 2002 05:10:10 -0000 1.5
--- pep-0280.txt 11 Feb 2002 07:05:01 -0000 1.6
***************
*** 184,188 ****
function's globals dict.
! Additional ideas:
- Never make func_cell a NULL pointer; instead, make up an array
--- 184,189 ----
function's globals dict.
!
! Additional Ideas
- Never make func_cell a NULL pointer; instead, make up an array
***************
*** 205,209 ****
return c.cellptr.objptr # Built-in or NULL
! XXX Incorporate Tim's most recent posts. (Tim, can you do this?)
--- 206,396 ----
return c.cellptr.objptr # Built-in or NULL
! - Be more aggressive: put the actual values of builtins into module
! dicts, not just pointers to cells containing the actual values.
!
! There are two points to this: (1) Simplify and speed access, which
! is the most common operation. (2) Support faithful emulation of
! extreme existing corner cases.
!
! WRT #2, the set of builtins in the scheme above is captured at the
! time a module dict is first created. Mutations to the set of builtin
! names following that don't get reflected in the module dicts. Example:
! consider files main.py and cheater.py:
!
! [main.py]
! import cheater
! def f():
! cheater.cheat()
! return pachinko()
! print f()
!
! [cheater.py]
! def cheat():
! import __builtin__
! __builtin__.pachinko = lambda: 666
!
! If main.py is run under Python 2.2 (or before), 666 is printed. But
! under the proposal, __builtin__.pachinko doesn't exist at the time
! main's __dict__ is initialized. When the function object for
! f is created, main.__dict__ grows a pachinko cell mapping to two
! NULLs. When cheat() is called, __builtin__.__dict__ grows a pachinko
! cell too, but main.__dict__ doesn't know-- and will never know --about
! that. When f's return stmt references pachinko, in will still find
! the double-NULLs in main.__dict__'s pachinko cell, and so raise
! NameError.
!
! A similar (in cause) break in compatibility can occur if a module
! global foo is del'ed, but a builtin foo was created prior to that
! but after the module dict was first created. Then the builtin foo
! becomes visible in the module under 2.2 and before, but remains
! invisible under the proposal.
!
! Mutating builtins is extremely rare (most programs never mutate the
! builtins, and it's hard to imagine a plausible use for frequent
! mutation of the builtins -- I've never seen or heard of one), so it
! doesn't matter how expensive mutating the builtins becomes. OTOH,
! referencing globals and builtins is very common. Combining those
! observations suggests a more aggressive caching of builtins in module
! globals, speeding access at the expense of making mutations of the
! builtins (potentially much) more expensive to keep the caches in
! synch.
!
! Much of the scheme above remains the same, and most of the rest is
! just a little different. A cell changes to:
!
! class cell(object):
! def __init__(self, obj=NULL, builtin=0):
! self.objptr = obj
! self.buitinflag = builtin
!
! and a celldict maps strings to this version of cells. builtinflag
! is true when and only when objptr contains a value obtained from
! the builtins; in other words, it's true when and only when a cell
! is acting as a cached value. When builtinflag is false, objptr is
! the value of a module global (possibly NULL). celldict changes to:
!
! class celldict(object):
!
! def __init__(self, builtindict=()):
! self.basedict = builtindict
! self.__dict = d = {}
! for k, v in builtindict.items():
! d[k] = cell(v, 1)
!
! def __getitem__(self, key):
! c = self.__dict.get(key)
! if c is None or c.objptr is NULL or c.builtinflag:
! raise KeyError, key
! return c.objptr
!
! def __setitem__(self, key, value):
! c = self.__dict.get(key)
! if c is None:
! c = cell()
! self.__dict[key] = c
! c.objptr = value
! c.builtinflag = 0
!
! def __delitem__(self, key):
! c = self.__dict.get(key)
! if c is None or c.objptr is NULL or c.builtinflag:
! raise KeyError, key
! c.objptr = NULL
! # We may have unmasked a builtin. Note that because
! # we're checking the builtin dict for that *now*, this
! # still works if the builtin first came into existence
! # after we were constructed. Note too that del on
! # namespace dicts is rare, so the expensse of this check
! # shouldn't matter.
! if key in self.basedict:
! c.objptr = self.basedict[key]
! assert c.objptr is not NULL # else "in" lied
! c.buitinflag = 1
! else:
! # There is no builtin with the same name.
! assert not c.buitinflag
!
! def keys(self):
! return [k for k, c in self.__dict.iteritems()
! if c.objptr is not NULL and not c.buitinflag]
!
! def items(self):
! return [k, c.objptr for k, c in self.__dict.iteritems()
! if c.objptr is not NULL and not c.buitinflag]
!
! def values(self):
! preturn [c.objptr for c in self.__dict.itervalues()
! if c.objptr is not NULL and not c.buitinflag]
!
! def clear(self):
! for c in self.__dict.values():
! if not c.buitinflag:
! c.objptr = NULL
!
! # Etc.
!
! The speed benefit comes from simplifying LOAD_GLOBAL_CELL, which
! I expect is executed more frequently than all other namespace
! operations combined:
!
! def LOAD_GLOBAL_CELL(self, i):
! # self is the frame
! c = self.func_cells[i]
! return c.objptr # may be NULL (also true before)
!
! That is, accessing builtins and accessing module globals are equally
! fast. For module globals, a NULL-pointer test+branch is saved. For
! builtins, an additional pointer chase is also saved.
!
! The other part needed to make this fly is expensive, propagating
! mutations of builtins into the module dicts that were initialized
! from the builtins. This is much like, in 2.2, propagating changes
! in new-style base classes to their descendants: the builtins need to
! maintain a list of weakrefs to the modules (or module dicts)
! initialized from the builtin's dict. Given a mutation to the builtin
! dict (adding a new key, changing the value associated with an
! existing key, or deleting a key), traverse the list of module dicts
! and make corresponding mutations to them. This is straightforward;
! for example, if a key is deleted from builtins, execute
! reflect_bltin_del in each module:
!
! def reflect_bltin_del(self, key):
! c = self.__dict.get(key)
! assert c is not None # else we were already out of synch
! if c.buitinflag:
! # Put us back in synch.
! c.objptr = NULL
! c.buitinflag = 0
! # Else we're shadowing the builtin, so don't care that
! # the builtin went away.
!
! Note that c.buitinflag protects from us erroneously deleting a
! module global of the same name. Adding a new (key, value) builtin
! pair is similar:
!
! def reflect_bltin_new(self, key, value):
! c = self.__dict.get(key)
! if c is None:
! # Never heard of it before: cache the builtin value.
! self.__dict[key] = cell(value, 1)
! elif c.objptr is NULL:
! # This used to exist in the module or the builtins,
! # but doesn't anymore; rehabilitate it.
! assert not c.builtinflag
! c.objptr = value
! c.buitinflag = 1
! else:
! # We're shadowing it already.
! assert not c.buitinflag
!
! Changing the value of an existing builtin can be viewed as deleting
! the name, then adding it again. Indeed, since mutating builtins is
! so rare, that's probably the right way to implement it too (speed
! doesn't matter here):
!
! def reflect_bltin_change(self, key, newvalue):
! assert key in self.__dict
! self.reflect_bltin_del(key)
! self.reflect_bltin_new(key, newvalue)