Skip to content

Commit

Permalink
Make all classes new-style.
Browse files Browse the repository at this point in the history
On PyPy, it's documented (http://pypy.org/performance.html#id3) that
old-style classes are slow, and classes that derive from both old and
new are especially slow. I checked with the PyPy devs on IRC today and
they confirmed that's still true with the latest PyPy2 releases.

Unfortunately, FileStorage was one such mixed class, as was Connection.

Moving to new-style classes seems to have a positive impact in the
benchmarks. Here's zodbshootout on PyPy2 5.7.1 against a
FileStorage (running in --threads and with 5 reps to be sure we get
warmed up). First, current ZODB:

"Transaction",                fs
"Add 1000 Objects",            31,738
"Update 1000 Objects",         42,444
"Read 1000 Cold Objects",      51,894
"Read 1000 Hot Objects",       53,187
"Read 1000 Steamin' Objects", 835,877

And with this PR:

"Transaction",                fs
"Add 1000 Objects",             35,651
"Update 1000 Objects",          54,906
"Read 1000 Cold Objects",      103,484
"Read 1000 Hot Objects",        84,721
"Read 1000 Steamin' Objects", 2,112,095

The tests that hit the storage extensively are notably faster, as are
steamin and hot, Connection having been a mixed class.

I ran all tests multiple times. The data files were removed between
runs. There's some variation, but the new-style classes always seem
better.

For comparison, here's CPython 2.7.13:

"Transaction",                fs
"Add 1000 Objects",            19,531
"Update 1000 Objects",         16,201
"Read 1000 Cold Objects",      22,111
"Read 1000 Hot Objects",       21,851
"Read 1000 Steamin' Objects", 880,582

Locally I've run the tests under 2.7 and they all passed.
  • Loading branch information
jamadden committed Apr 13, 2017
1 parent 4b500e7 commit a63d77c
Show file tree
Hide file tree
Showing 50 changed files with 79 additions and 77 deletions.
4 changes: 3 additions & 1 deletion CHANGES.rst
Expand Up @@ -5,7 +5,9 @@
5.2.4 (unreleased)
==================

- Nothing changed yet.
- All classes are new-style classes on Python 2 (they were already
new-style on Python 3). This improves performance on PyPy. See
`issue 159 <<https://github.com/zopefoundation/ZODB/issues/158>`_.


5.2.3 (2017-04-11)
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/ActivityMonitor.py
Expand Up @@ -19,7 +19,7 @@
from . import utils


class ActivityMonitor:
class ActivityMonitor(object):
"""ZODB load/store activity monitor
This simple implementation just keeps a small log in memory
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/ConflictResolution.py
Expand Up @@ -211,7 +211,7 @@ def klass(self):
elif isinstance(data, list) and data[0] == 'm':
return data[1][2]

class PersistentReferenceFactory:
class PersistentReferenceFactory(object):

data = None

Expand Down
4 changes: 2 additions & 2 deletions src/ZODB/Connection.py
Expand Up @@ -1120,7 +1120,7 @@ def _prefetch_flatten(self, args):
yield ob._p_oid

@implementer(IDataManagerSavepoint)
class Savepoint:
class Savepoint(object):

def __init__(self, datamanager, state):
self.datamanager = datamanager
Expand All @@ -1131,7 +1131,7 @@ def rollback(self):


@implementer(IBlobStorage)
class TmpStore:
class TmpStore(object):
"""A storage-like thing to support savepoints."""


Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/DB.py
Expand Up @@ -1005,7 +1005,7 @@ def open_then_close_db_when_connection_closes(self):
return conn


class ContextManager:
class ContextManager(object):
"""PEP 343 context manager
"""

Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/ExportImport.py
Expand Up @@ -29,7 +29,7 @@

logger = logging.getLogger('ZODB.ExportImport')

class ExportImport:
class ExportImport(object):

def exportFile(self, oid, f=None):
if f is None:
Expand Down
4 changes: 2 additions & 2 deletions src/ZODB/FileStorage/FileStorage.py
Expand Up @@ -2083,7 +2083,7 @@ def __init__(self, oid, tid, data, prev, pos):
self.pos = pos


class UndoSearch:
class UndoSearch(object):

def __init__(self, file, pos, first, last, filter=None):
self.file = file
Expand Down Expand Up @@ -2144,7 +2144,7 @@ def _readnext(self):
d.update(e)
return d

class FilePool:
class FilePool(object):

closed = False
writing = False
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/FileStorage/fsdump.py
Expand Up @@ -56,7 +56,7 @@ def fmt(p64):
# Return a nicely formatted string for a packaged 64-bit value
return "%016x" % u64(p64)

class Dumper:
class Dumper(object):
"""A very verbose dumper for debuggin FileStorage problems."""

# TODO: Should revise this class to use FileStorageFormatter.
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/MappingStorage.py
Expand Up @@ -332,7 +332,7 @@ def tpc_vote(self, transaction):
raise ZODB.POSException.StorageTransactionError(
"tpc_vote called with wrong transaction")

class TransactionRecord:
class TransactionRecord(object):

status = ' '

Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/UndoLogCompatible.py
Expand Up @@ -14,7 +14,7 @@
"""Provide backward compatibility with storages that only have undoLog()."""


class UndoLogCompatible:
class UndoLogCompatible(object):

def undoInfo(self, first=0, last=-20, specification=None):
if specification:
Expand Down
4 changes: 2 additions & 2 deletions src/ZODB/blob.py
Expand Up @@ -345,7 +345,7 @@ def log(msg, level=logging.INFO, subsys=_pid, exc_info=False):
logger.log(level, message, exc_info=exc_info)


class FilesystemHelper:
class FilesystemHelper(object):
# Storages that implement IBlobStorage can choose to use this
# helper class to generate and parse blob filenames. This is not
# a set-in-stone interface for all filesystem operations dealing
Expand Down Expand Up @@ -519,7 +519,7 @@ def listOIDs(self):
yield oid, path


class NoBlobsFileSystemHelper:
class NoBlobsFileSystemHelper(object):

@property
def temp_dir(self):
Expand Down
4 changes: 2 additions & 2 deletions src/ZODB/broken.py
Expand Up @@ -163,7 +163,7 @@ def find_global(modulename, globalname,
If we "repair" a missing global::
>>> class ZODBnotthere:
>>> class ZODBnotthere(object):
... atall = []
>>> sys.modules['ZODB.not'] = ZODBnotthere
Expand Down Expand Up @@ -233,7 +233,7 @@ def rebuild(modulename, globalname, *args):
If we "repair" the brokenness::
>>> class notthere: # fake notthere module
>>> class notthere(object): # fake notthere module
... class atall(object):
... def __new__(self, *args):
... ob = object.__new__(self)
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/config.py
Expand Up @@ -103,7 +103,7 @@ def storageFromURL(url):
def storageFromConfig(section):
return section.open()

class BaseConfig:
class BaseConfig(object):
"""Object representing a configured storage or database.
Methods:
Expand Down
4 changes: 2 additions & 2 deletions src/ZODB/conversionhack.py
Expand Up @@ -14,7 +14,7 @@

import persistent.mapping

class fixer:
class fixer(object):
def __of__(self, parent):
def __setstate__(state, self=parent):
self._container=state
Expand All @@ -23,7 +23,7 @@ def __setstate__(state, self=parent):

fixer=fixer()

class hack: pass
class hack(object): pass
hack=hack()

def __basicnew__():
Expand Down
4 changes: 2 additions & 2 deletions src/ZODB/fstools.py
Expand Up @@ -27,7 +27,7 @@
from persistent.TimeStamp import TimeStamp


class TxnHeader:
class TxnHeader(object):
"""Object representing a transaction record header.
Attribute Position Value
Expand Down Expand Up @@ -100,7 +100,7 @@ def prev_txn(self):
tlen = u64(self._file.read(8))
return TxnHeader(self._file, self._pos - (tlen + 8))

class DataHeader:
class DataHeader(object):
"""Object representing a data record header.
Attribute Position Value
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/scripts/analyze.py
Expand Up @@ -26,7 +26,7 @@ def FakeUnpickler(f):
return unpickler


class Report:
class Report(object):
def __init__(self):
self.OIDMAP = {}
self.TYPEMAP = {}
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/scripts/fstest.py
Expand Up @@ -44,7 +44,7 @@
class FormatError(ValueError):
"""There is a problem with the format of the FileStorage."""

class Status:
class Status(object):
checkpoint = b'c'
undone = b'u'

Expand Down
4 changes: 2 additions & 2 deletions src/ZODB/scripts/migrate.py
Expand Up @@ -112,7 +112,7 @@ def main():
except getopt.error as msg:
error(2, msg)

class Options:
class Options(object):
stype = 'FileStorage'
dtype = 'FileStorage'
verbose = 0
Expand Down Expand Up @@ -329,7 +329,7 @@ def doit(srcdb, dstdb, options):
# helper to deal with differences between old-style store() return and
# new-style store() return that supports ZEO

class RevidAccumulator:
class RevidAccumulator(object):

def __init__(self):
self.data = {}
Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/scripts/repozo.py
Expand Up @@ -164,7 +164,7 @@ def parseargs(argv):
except getopt.error as msg:
usage(1, msg)

class Options:
class Options(object):
mode = None # BACKUP, RECOVER or VERIFY
file = None # name of input Data.fs file
repository = None # name of directory holding backups
Expand Down
8 changes: 4 additions & 4 deletions src/ZODB/scripts/tests/test_repozo.py
Expand Up @@ -38,7 +38,7 @@ def _read_file(name, mode='rb'):
return f.read()


class OurDB:
class OurDB(object):

_file_name = None

Expand Down Expand Up @@ -241,7 +241,7 @@ def test_verify_ignored_args(self):
sys.stderr.getvalue())


class FileopsBase:
class FileopsBase(object):

def _makeChunks(self):
from ZODB.scripts.repozo import READCHUNK
Expand Down Expand Up @@ -316,7 +316,7 @@ def test_nonempty_read_count(self):
self.assertEqual(sum, md5(b'x' * 42).hexdigest())


class OptionsTestBase:
class OptionsTestBase(object):

_repository_directory = None
_data_directory = None
Expand Down Expand Up @@ -408,7 +408,7 @@ def test_w_gzipped_files_no_ofp(self):

def test_w_ofp(self):

class Faux:
class Faux(object):
_closed = False
def __init__(self):
self._written = []
Expand Down
12 changes: 6 additions & 6 deletions src/ZODB/scripts/zodbload.py
Expand Up @@ -123,7 +123,7 @@
import time
import transaction

class JobProducer:
class JobProducer(object):

def __init__(self):
self.jobs = []
Expand All @@ -143,7 +143,7 @@ def __nonzero__(self):



class MBox:
class MBox(object):

def __init__(self, filename):
if ' ' in filename:
Expand Down Expand Up @@ -247,7 +247,7 @@ def setup(lib_python):
PLexicon('lex', '', Splitter(), CaseNormalizer())
)

class extra:
class extra(object):
doc_attr = 'PrincipiaSearchSource'
lexicon_id = 'lex'
index_type = 'Okapi BM25 Rank'
Expand Down Expand Up @@ -371,7 +371,7 @@ def index(connection, messages, catalog, max):

return message.number

class IndexJob:
class IndexJob(object):
needs_mbox = 1
catalog = 1
prefix = 'index'
Expand Down Expand Up @@ -444,7 +444,7 @@ def edit(connection, mbox, catalog=1):

return norig, ndel, nins

class EditJob:
class EditJob(object):
needs_mbox = 1
prefix = 'edit'
catalog = 1
Expand Down Expand Up @@ -480,7 +480,7 @@ def search(connection, terms, number):

return n

class SearchJob:
class SearchJob(object):

def __init__(self, terms='', number=10):

Expand Down
14 changes: 7 additions & 7 deletions src/ZODB/serialize.py
Expand Up @@ -159,7 +159,7 @@ def myhasattr(obj, name, _marker=object()):
return getattr(obj, name, _marker) is not _marker


class ObjectWriter:
class ObjectWriter(object):
"""Serializes objects for storage in the database.
The ObjectWriter creates object pickles in the ZODB format. It
Expand All @@ -183,7 +183,7 @@ def persistent_id(self, obj):
"""Return the persistent id for obj.
>>> from ZODB.tests.util import P
>>> class DummyJar:
>>> class DummyJar(object):
... xrefs = True
... def new_oid(self):
... return 42
Expand All @@ -192,7 +192,7 @@ def persistent_id(self, obj):
... databases = {}
>>> jar = DummyJar()
>>> class O:
>>> class O(object):
... _p_jar = jar
>>> writer = ObjectWriter(O)
Expand Down Expand Up @@ -260,7 +260,7 @@ def persistent_id(self, obj):
Check that a classic class doesn't get identified improperly:
>>> class ClassicClara:
>>> class ClassicClara(object):
... pass
>>> clara = ClassicClara()
Expand Down Expand Up @@ -432,7 +432,7 @@ def _dump(self, classmeta, state):
def __iter__(self):
return NewObjectIterator(self._stack)

class NewObjectIterator:
class NewObjectIterator(object):

# The pickler is used as a forward iterator when the connection
# is looking for new objects to pickle.
Expand All @@ -452,7 +452,7 @@ def __next__(self):

next = __next__

class ObjectReader:
class ObjectReader(object):

def __init__(self, conn=None, cache=None, factory=None):
self._conn = conn
Expand Down Expand Up @@ -680,7 +680,7 @@ def get_refs(a_pickle):
u.noload()

# Now we have a list of references. Need to convert to list of
# oids and class info:
# oids and class info(object):

result = []

Expand Down
2 changes: 1 addition & 1 deletion src/ZODB/tests/BasicStorage.py
Expand Up @@ -32,7 +32,7 @@

ZERO = b'\0'*8

class BasicStorage:
class BasicStorage(object):
def checkBasics(self):
self.assertEqual(self._storage.lastTransaction(), ZERO)

Expand Down

0 comments on commit a63d77c

Please sign in to comment.