Commit 162bcdf9 authored by Jason Madden's avatar Jason Madden

Optimizations. Primarily, this changes _p_accessed to avoid recursively...

Optimizations.  Primarily, this changes _p_accessed to avoid recursively calling back to __getattribute__. When running the ZODB test suite, this changes the number of times that __getattribute__ is called from over 44 MILLION to just 1.3 million (and changes the cumulative time from 38 seconds to 11 seconds).

Secondarily, this potentially also makes a few things safer for Broken objects.
parent 27c8784c
...@@ -42,10 +42,9 @@ SPECIAL_NAMES = ('__class__', ...@@ -42,10 +42,9 @@ SPECIAL_NAMES = ('__class__',
'__del__', '__del__',
'__dict__', '__dict__',
'__of__', '__of__',
'__setstate__' '__setstate__',
) )
@implementer(IPersistent) @implementer(IPersistent)
class Persistent(object): class Persistent(object):
""" Pure Python implmentation of Persistent base class """ Pure Python implmentation of Persistent base class
...@@ -238,13 +237,14 @@ class Persistent(object): ...@@ -238,13 +237,14 @@ class Persistent(object):
def __getattribute__(self, name): def __getattribute__(self, name):
""" See IPersistent. """ See IPersistent.
""" """
oga = _OGA
if (not name.startswith('_Persistent__') and if (not name.startswith('_Persistent__') and
not name.startswith('_p_') and not name.startswith('_p_') and
name not in SPECIAL_NAMES): name not in SPECIAL_NAMES):
if _OGA(self, '_Persistent__flags') is None: if oga(self, '_Persistent__flags') is None:
_OGA(self, '_p_activate')() oga(self, '_p_activate')()
_OGA(self, '_p_accessed')() oga(self, '_p_accessed')()
return _OGA(self, name) return oga(self, name)
def __setattr__(self, name, value): def __setattr__(self, name, value):
special_name = (name.startswith('_Persistent__') or special_name = (name.startswith('_Persistent__') or
...@@ -339,15 +339,22 @@ class Persistent(object): ...@@ -339,15 +339,22 @@ class Persistent(object):
def _p_activate(self): def _p_activate(self):
""" See IPersistent. """ See IPersistent.
""" """
before = self.__flags oga = _OGA
if self.__flags is None or self._p_state < 0: # Only do this if we're a ghost before = oga(self, '_Persistent__flags')
_OSA(self, '_Persistent__flags', 0) if before is None: # Only do this if we're a ghost
if self.__jar is not None and self.__oid is not None: _OSA(self, '_Persistent__flags', 0) # up-to-date
try: jar = oga(self, '_Persistent__jar')
self.__jar.setstate(self) if jar is None:
except: return
_OSA(self, '_Persistent__flags', before) oid = oga(self, '_Persistent__oid')
raise if oid is None:
return
try:
jar.setstate(self)
except:
_OSA(self, '_Persistent__flags', before)
raise
# In the C implementation, _p_invalidate winds up calling # In the C implementation, _p_invalidate winds up calling
# _p_deactivate. There are ZODB tests that depend on this; # _p_deactivate. There are ZODB tests that depend on this;
...@@ -443,24 +450,41 @@ class Persistent(object): ...@@ -443,24 +450,41 @@ class Persistent(object):
# detail, the '_cache' attribute of the jar. We made it a # detail, the '_cache' attribute of the jar. We made it a
# private API to avoid the cycle of keeping a reference to # private API to avoid the cycle of keeping a reference to
# the cache on the persistent object. # the cache on the persistent object.
if (self.__jar is not None and
self.__oid is not None and # The below is the equivalent of this, but avoids
self._p_state >= 0): # several trips through __getattribute__, especially for _p_state,
# The KeyError arises in ZODB: ZODB.serialize.ObjectWriter # and benchmarks much faster
# can assign a jar and an oid to newly seen persistent objects, #
# but because they are newly created, they aren't in the # if(self.__jar is None or
# pickle cache yet. There doesn't seem to be a way to distinguish # self.__oid is None or
# that at this level, all we can do is catch it. # self._p_state < 0 ): return
# The AttributeError arises in ZODB test cases
oga = _OGA
jar = oga(self, '_Persistent__jar')
if jar is None:
return
oid = oga(self, '_Persistent__oid')
if oid is None:
return
flags = oga(self, '_Persistent__flags')
if flags is None: # ghost
return
# The KeyError arises in ZODB: ZODB.serialize.ObjectWriter
# can assign a jar and an oid to newly seen persistent objects,
# but because they are newly created, they aren't in the
# pickle cache yet. There doesn't seem to be a way to distinguish
# that at this level, all we can do is catch it.
# The AttributeError arises in ZODB test cases
try:
cache = jar._cache
except AttributeError:
pass
else:
try: try:
cache = self.__jar._cache cache.mru(self.__oid)
except AttributeError: except KeyError:
pass pass
else:
try:
cache.mru(self.__oid)
except KeyError:
pass
def _p_is_in_cache(self): def _p_is_in_cache(self):
oid = self.__oid oid = self.__oid
......
...@@ -1522,6 +1522,27 @@ class PyPersistentTests(unittest.TestCase, _Persistent_Base): ...@@ -1522,6 +1522,27 @@ class PyPersistentTests(unittest.TestCase, _Persistent_Base):
c1._p_invalidate_deactivate_helper() c1._p_invalidate_deactivate_helper()
self.assertTrue(c1._p_jar.accessed) self.assertTrue(c1._p_jar.accessed)
def test_p_activate_with_jar_without_oid(self):
# Works, but nothing happens
inst = self._makeOne()
inst._p_jar = object()
inst._p_oid = None
object.__setattr__(inst, '_Persistent__flags', None)
inst._p_activate()
def test_p_accessed_with_jar_without_oid(self):
# Works, but nothing happens
inst = self._makeOne()
inst._p_jar = object()
inst._p_accessed()
def test_p_accessed_with_jar_with_oid_as_ghost(self):
# Works, but nothing happens
inst = self._makeOne()
inst._p_jar = object()
inst._p_oid = 42
inst._Persistent__flags = None
inst._p_accessed()
_add_to_suite = [PyPersistentTests] _add_to_suite = [PyPersistentTests]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment