Commit 6fa457df authored by Jason Madden's avatar Jason Madden

Optimize the _p_accessed code path in pure-python for substantial performance improvements.

Some very minor tweaks to avoid going through the __getattribute__ path so much have large consequences for `zodbshootout`:

  Results show objects written or read per second. Best of 3. (before/after on pypy2.5.1)
  "Transaction",                before        after      cpython2.7
  "Add 3000 Objects",             534         2340        11711
  "Update 3000 Objects",          330         3035        11333
  "Read 3000 Warm Objects",      1168         1941         9086
  "Read 3000 Cold Objects",      1647         4161         9484
  "Read 3000 Hot Objects",       2647        11086        45220
  "Read 3000 Steamin' Objects", 30523       108138      5321032
parent 87f185c6
......@@ -71,7 +71,7 @@ class Persistent(object):
# _p_jar: see IPersistent.
def _get_jar(self):
return self.__jar
return _OGA(self, '_Persistent__jar')
def _set_jar(self, value):
if self._p_is_in_cache() and value is not None and self.__jar != value:
......@@ -95,7 +95,7 @@ class Persistent(object):
# _p_oid: see IPersistent.
def _get_oid(self):
return self.__oid
return _OGA(self, '_Persistent__oid')
def _set_oid(self, value):
if value == self.__oid:
......@@ -175,15 +175,18 @@ class Persistent(object):
# _p_state
def _get_state(self):
if self.__jar is None:
# Note the use of OGA and caching to avoid recursive calls to __getattribute__:
# __getattribute__ calls _p_accessed calls cache.mru() calls _p_state
if _OGA(self, '_Persistent__jar') is None:
return UPTODATE
if self.__flags is None:
flags = _OGA(self, '_Persistent__flags')
if flags is None:
return GHOST
if self.__flags & _CHANGED:
if flags & _CHANGED:
result = CHANGED
else:
result = UPTODATE
if self.__flags & _STICKY:
if flags & _STICKY:
return STICKY
return result
......@@ -250,7 +253,7 @@ class Persistent(object):
return oga(self, name)
def __setattr__(self, name, value):
special_name = (name.startswith('_Persistent__') or
special_name = (name in _SPECIAL_NAMES or
name.startswith('_p_'))
volatile = name.startswith('_v_')
if not special_name:
......@@ -270,7 +273,7 @@ class Persistent(object):
_OGA(self, '_p_register')()
def __delattr__(self, name):
special_name = (name.startswith('_Persistent__') or
special_name = (name in _SPECIAL_NAMES or
name.startswith('_p_'))
if not special_name:
if _OGA(self, '_Persistent__flags') is None:
......@@ -469,7 +472,7 @@ class Persistent(object):
# the cache on the persistent object.
# The below is the equivalent of this, but avoids
# several trips through __getattribute__, especially for _p_state,
# several recursive through __getattribute__, especially for _p_state,
# and benchmarks much faster
#
# if(self.__jar is None or
......
......@@ -54,6 +54,8 @@ else:
def _gc_monitor(o):
pass
_OGA = object.__getattribute__
class RingNode(object):
# 32 byte fixed size wrapper.
__slots__ = ('object', 'next', 'prev')
......@@ -201,18 +203,29 @@ class PickleCache(object):
# overridden _p_deactivate, don't mutate the ring
# because that could leave it inconsistent
return False # marker return for tests
# Under certain benchmarks, like zodbshootout, this method is
# the primary bottleneck (compare 33,473 "steamin" objects per
# second in the original version of this function with 542,144
# objects per second if this function simply returns), so we
# take a few steps to reduce the pressure:
# * object.__getattribute__ here to avoid recursive calls
# back to Persistent.__getattribute__. This alone makes a 50%
# difference in zodbshootout performance (55,000 OPS)
node = self.ring.next
while node is not self.ring and node.object._p_oid != oid:
while node is not self.ring and _OGA(node.object, '_p_oid') != oid:
node = node.next
if node is self.ring:
value = self.data[oid]
if value._p_state != GHOST:
if _OGA(value, '_p_state') != GHOST:
self.non_ghost_count += 1
mru = self.ring.prev
self.ring.prev = node = RingNode(value, self.ring, mru)
mru.next = node
else:
assert node.object._p_oid == oid
# This assertion holds, but it's a redundant getattribute access
#assert node.object._p_oid == oid
# remove from old location
node.prev.next, node.next.prev = node.next, node.prev
# splice into new
......
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