Commit e714f515 authored by Jason Madden's avatar Jason Madden Committed by Jim Fulton

Make all classes new-style. (#160)

* Make all classes new-style.

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.
parent 3580ddd8
......@@ -5,15 +5,17 @@
5.2.4 (unreleased)
==================
- Optimize getting the path to a blob file.
- Optimize getting the path to a blob file. See
`issue 161 <<https://github.com/zopefoundation/ZODB/pull/161>`_.
- All classes are new-style classes on Python 2 (they were already
new-style on Python 3). This improves performance on PyPy. See
`issue 160 <<https://github.com/zopefoundation/ZODB/pull/160>`_.
5.2.3 (2017-04-11)
==================
- Fix an import error. See `issue 158 <https://github.com/zopefoundation/ZODB/issues/158>`_.
5.2.2 (2017-04-11)
==================
......
......@@ -19,7 +19,7 @@ import time
from . import utils
class ActivityMonitor:
class ActivityMonitor(object):
"""ZODB load/store activity monitor
This simple implementation just keeps a small log in memory
......
......@@ -211,7 +211,7 @@ class PersistentReference(object):
elif isinstance(data, list) and data[0] == 'm':
return data[1][2]
class PersistentReferenceFactory:
class PersistentReferenceFactory(object):
data = None
......
......@@ -1120,7 +1120,7 @@ class Connection(ExportImport, object):
yield ob._p_oid
@implementer(IDataManagerSavepoint)
class Savepoint:
class Savepoint(object):
def __init__(self, datamanager, state):
self.datamanager = datamanager
......@@ -1131,7 +1131,7 @@ class Savepoint:
@implementer(IBlobStorage)
class TmpStore:
class TmpStore(object):
"""A storage-like thing to support savepoints."""
......
......@@ -1005,7 +1005,7 @@ class DB(object):
return conn
class ContextManager:
class ContextManager(object):
"""PEP 343 context manager
"""
......
......@@ -29,7 +29,7 @@ from ZODB._compat import PersistentPickler, Unpickler, BytesIO, _protocol
logger = logging.getLogger('ZODB.ExportImport')
class ExportImport:
class ExportImport(object):
def exportFile(self, oid, f=None):
if f is None:
......
......@@ -2083,7 +2083,7 @@ class Record(_DataRecord):
self.pos = pos
class UndoSearch:
class UndoSearch(object):
def __init__(self, file, pos, first, last, filter=None):
self.file = file
......@@ -2144,7 +2144,7 @@ class UndoSearch:
d.update(e)
return d
class FilePool:
class FilePool(object):
closed = False
writing = False
......
......@@ -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.
......
......@@ -332,7 +332,7 @@ class MappingStorage(object):
raise ZODB.POSException.StorageTransactionError(
"tpc_vote called with wrong transaction")
class TransactionRecord:
class TransactionRecord(object):
status = ' '
......
......@@ -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:
......
......@@ -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
......@@ -519,7 +519,7 @@ class FilesystemHelper:
yield oid, path
class NoBlobsFileSystemHelper:
class NoBlobsFileSystemHelper(object):
@property
def temp_dir(self):
......
......@@ -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
......@@ -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)
......
......@@ -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:
......
......@@ -14,7 +14,7 @@
import persistent.mapping
class fixer:
class fixer(object):
def __of__(self, parent):
def __setstate__(state, self=parent):
self._container=state
......@@ -23,7 +23,7 @@ class fixer:
fixer=fixer()
class hack: pass
class hack(object): pass
hack=hack()
def __basicnew__():
......
......@@ -27,7 +27,7 @@ from ZODB._compat import loads
from persistent.TimeStamp import TimeStamp
class TxnHeader:
class TxnHeader(object):
"""Object representing a transaction record header.
Attribute Position Value
......@@ -100,7 +100,7 @@ class TxnHeader:
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
......
......@@ -26,7 +26,7 @@ def FakeUnpickler(f):
return unpickler
class Report:
class Report(object):
def __init__(self):
self.OIDMAP = {}
self.TYPEMAP = {}
......
......@@ -44,7 +44,7 @@ from ZODB._compat import FILESTORAGE_MAGIC
class FormatError(ValueError):
"""There is a problem with the format of the FileStorage."""
class Status:
class Status(object):
checkpoint = b'c'
undone = b'u'
......
......@@ -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
......@@ -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 = {}
......
......@@ -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
......
......@@ -38,7 +38,7 @@ def _read_file(name, mode='rb'):
return f.read()
class OurDB:
class OurDB(object):
_file_name = None
......@@ -241,7 +241,7 @@ class Test_parseargs(unittest.TestCase):
sys.stderr.getvalue())
class FileopsBase:
class FileopsBase(object):
def _makeChunks(self):
from ZODB.scripts.repozo import READCHUNK
......@@ -316,7 +316,7 @@ class Test_checksum(unittest.TestCase, FileopsBase):
self.assertEqual(sum, md5(b'x' * 42).hexdigest())
class OptionsTestBase:
class OptionsTestBase(object):
_repository_directory = None
_data_directory = None
......@@ -408,7 +408,7 @@ class Test_concat(OptionsTestBase, unittest.TestCase):
def test_w_ofp(self):
class Faux:
class Faux(object):
_closed = False
def __init__(self):
self._written = []
......
......@@ -123,7 +123,7 @@ import threading
import time
import transaction
class JobProducer:
class JobProducer(object):
def __init__(self):
self.jobs = []
......@@ -143,7 +143,7 @@ class JobProducer:
class MBox:
class MBox(object):
def __init__(self, filename):
if ' ' in filename:
......@@ -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'
......@@ -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'
......@@ -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
......@@ -480,7 +480,7 @@ def search(connection, terms, number):
return n
class SearchJob:
class SearchJob(object):
def __init__(self, terms='', number=10):
......
......@@ -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
......@@ -183,7 +183,7 @@ class ObjectWriter:
"""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
......@@ -192,7 +192,7 @@ class ObjectWriter:
... databases = {}
>>> jar = DummyJar()
>>> class O:
>>> class O(object):
... _p_jar = jar
>>> writer = ObjectWriter(O)
......@@ -260,7 +260,7 @@ class ObjectWriter:
Check that a classic class doesn't get identified improperly:
>>> class ClassicClara:
>>> class ClassicClara(object):
... pass
>>> clara = ClassicClara()
......@@ -432,7 +432,7 @@ class ObjectWriter:
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.
......@@ -452,7 +452,7 @@ class NewObjectIterator:
next = __next__
class ObjectReader:
class ObjectReader(object):
def __init__(self, conn=None, cache=None, factory=None):
self._conn = conn
......
......@@ -32,7 +32,7 @@ from .. import utils
ZERO = b'\0'*8
class BasicStorage:
class BasicStorage(object):
def checkBasics(self):
self.assertEqual(self._storage.lastTransaction(), ZERO)
......
......@@ -55,7 +55,7 @@ class PCounter4(PCounter):
def _p_resolveConflict(self, oldState, savedState):
raise RuntimeError("Can't get here; not enough args")
class ConflictResolvingStorage:
class ConflictResolvingStorage(object):
def checkResolve(self, resolvable=True):
db = DB(self._storage)
......@@ -131,7 +131,7 @@ class ConflictResolvingStorage:
self._dostoreNP,
oid, revid=revid1, data=zodb_pickle(obj))
class ConflictResolvingTransUndoStorage:
class ConflictResolvingTransUndoStorage(object):
def checkUndoConflictResolution(self):
# This test is based on checkNotUndoable in the
......
......@@ -21,7 +21,7 @@ import sys
from time import time, sleep
from ZODB.tests.MinPO import MinPO
class HistoryStorage:
class HistoryStorage(object):
def checkSimpleHistory(self):
self._checkHistory((11, 12, 13))
......
......@@ -31,7 +31,7 @@ except ImportError:
# Py3: zip() already returns an iterable.
pass
class IteratorCompare:
class IteratorCompare(object):
def iter_verify(self, txniter, revids, val0):
eq = self.assertEqual
......@@ -203,7 +203,7 @@ class ExtendedIteratorStorage(IteratorCompare):
self.iter_verify(txniter, [revid3], 13)
class IteratorDeepCompare:
class IteratorDeepCompare(object):
def compare(self, storage1, storage2):
eq = self.assertEqual
......
......@@ -211,7 +211,7 @@ class ExtStorageClientThread(StorageClientThread):
for obj in iter:
pass
class MTStorage:
class MTStorage(object):
"Test a storage with multiple client threads executing concurrently."
def _checkNThreads(self, n, constructor, *args):
......
......@@ -44,7 +44,7 @@ ZERO = b'\0'*8
# ids, not as the object's state. This makes the referencesf stuff work,
# because it pickle sniffs for persistent ids (so we have to get those
# persistent ids into the root object's pickle).
class Root:
class Root(object):
pass
......@@ -99,7 +99,7 @@ def pdumps(obj):
return s.getvalue()
class PackableStorageBase:
class PackableStorageBase(object):
# We keep a cache of object ids to instances so that the unpickler can
# easily return any persistent object.
......@@ -768,7 +768,7 @@ class ClientThread(TestThread):
conn.close()
class ElapsedTimer:
class ElapsedTimer(object):
def __init__(self, start_time):
self.start_time = start_time
......
......@@ -15,7 +15,7 @@
from ZODB.utils import load_current
class PersistentStorage:
class PersistentStorage(object):
def checkUpdatesPersist(self):
oids = []
......
......@@ -16,7 +16,7 @@ from ZODB.POSException import ReadOnlyError, Unsupported
from ZODB.utils import load_current
class ReadOnlyStorage:
class ReadOnlyStorage(object):
def _create_data(self):
# test a read-only storage that already has some data
......
......@@ -20,7 +20,7 @@ from ZODB.utils import p64, u64, load_current
ZERO = '\0'*8
class RevisionStorage:
class RevisionStorage(object):
def checkLoadSerial(self):
oid = self._storage.new_oid()
......
......@@ -69,7 +69,7 @@ OID = "\000" * 8
SERIALNO = "\000" * 8
TID = "\000" * 8
class SynchronizedStorage:
class SynchronizedStorage(object):
def verifyNotCommitting(self, callable, *args):
self.assertRaises(StorageTransactionError, callable, *args)
......
......@@ -53,7 +53,7 @@ def listeq(L1, L2):
"""
return sorted(L1) == sorted(L2)
class TransactionalUndoStorage:
class TransactionalUndoStorage(object):
def _multi_obj_transaction(self, objs):
t = TransactionMetaData()
......
......@@ -149,7 +149,7 @@ class Transaction(object):
def __getattr__(self, name):
return getattr(self.__trans, name)
class ZConfigHex:
class ZConfigHex(object):
_factory = HexStorage
......
......@@ -24,7 +24,7 @@ import time
from ZODB.ActivityMonitor import ActivityMonitor
class FakeConnection:
class FakeConnection(object):
loads = 0
stores = 0
......
......@@ -32,7 +32,7 @@ def test_integration():
We'll create a fake module with a class:
>>> class NotThere:
>>> class NotThere(object):
... Atall = type('Atall', (persistent.Persistent, ),
... {'__module__': 'ZODB.not.there'})
......
......@@ -307,7 +307,7 @@ class LRUCacheTests(CacheTestBase):
if details['state'] is None: # i.e., it's a ghost
self.assertTrue(details['rc'] > 0)
class StubDataManager:
class StubDataManager(object):
def setklassstate(self, object):
pass
......
......@@ -1213,7 +1213,7 @@ class ModifyOnGetStateObject(Persistent):
return Persistent.__getstate__(self)
class StubStorage:
class StubStorage(object):
"""Very simple in-memory storage that does *just* enough to support tests.
Only one concurrent transaction is supported.
......@@ -1373,7 +1373,7 @@ class TestConnection(unittest.TestCase):
db.close()
class StubDatabase:
class StubDatabase(object):
def __init__(self):
self.storage = StubStorage()
......
......@@ -160,7 +160,7 @@ def setUp(test):
def testSomeDelegation():
r"""
>>> import six
>>> class S:
>>> class S(object):
... def __init__(self, name):
... self.name = name
... def getSize(self):
......
......@@ -33,7 +33,7 @@ from ZODB.tests import (
Synchronization,
)
class MVCCTests:
class MVCCTests(object):
def checkClosingNestedDatabasesWorks(self):
# This tests for the error described in
......
......@@ -37,7 +37,7 @@ class TestPList(unittest.TestCase):
uu2 = PersistentList(u2)
v = PersistentList(tuple(u))
class OtherList:
class OtherList(object):
def __init__(self, initlist):
self.__data = initlist
def __len__(self):
......
......@@ -102,7 +102,7 @@ class SerializerTestCase(unittest.TestCase):
def test_myhasattr(self):
class OldStyle:
class OldStyle(object):
bar = "bar"
def __getattr__(self, name):
if name == "error":
......
......@@ -598,7 +598,7 @@ class PoisonedError(Exception):
pass
# PoisonedJar arranges to raise PoisonedError from interesting places.
class PoisonedJar:
class PoisonedJar(object):
def __init__(self, break_tpc_begin=False, break_tpc_vote=False,
break_savepoint=False):
self.break_tpc_begin = break_tpc_begin
......@@ -629,7 +629,7 @@ class PoisonedJar:
pass
class PoisonedObject:
class PoisonedObject(object):
def __init__(self, poisonedjar):
self._p_jar = poisonedjar
......
......@@ -52,7 +52,7 @@ class RegularObject(Persistent):
class PersistentObject(Persistent):
pass
class CacheTests:
class CacheTests(object):
def test_cache(self):
r"""Test basic cache methods.
......
......@@ -222,7 +222,7 @@ And load the pickle:
Oooooof course, this won't work if the subobjects aren't persistent:
>>> class NP:
>>> class NP(object):
... pass
......
......@@ -67,7 +67,7 @@ def test_new_ghost_w_persistent_class():
"""
# XXX need to update files to get newer testing package
class FakeModule:
class FakeModule(object):
def __init__(self, name, dict):
self.__dict__ = dict
self.__name__ = name
......
......@@ -99,7 +99,7 @@ class P(persistent.Persistent):
def __repr__(self):
return 'P(%s)' % self.name
class MininalTestLayer:
class MininalTestLayer(object):
__bases__ = ()
__module__ = ''
......
......@@ -13,7 +13,7 @@
##############################################################################
import warnings
class WarningsHook:
class WarningsHook(object):
"""Hook to capture warnings generated by Python.
The function warnings.showwarning() is designed to be hooked by
......
......@@ -293,7 +293,7 @@ class locked(object):
if os.environ.get('DEBUG_LOCKING'): # pragma: no cover
# NOTE: This only works on Python 3.
class Lock:
class Lock(object):
lock_class = threading.Lock
......
......@@ -3,7 +3,7 @@
See http://stackoverflow.com/questions/9153473/sphinx-values-for-attributes-reported-as-none/39276413
"""
class ValueDoc:
class ValueDoc(object):
def __init__(self, text):
self.text = text
......
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