diff options
author | Jason Madden <jamadden@gmail.com> | 2020-01-23 10:24:12 -0600 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2020-01-27 07:40:35 -0600 |
commit | ab0466e56c6328407c3839a3a392cb127dbdb282 (patch) | |
tree | d8d0653cf8d767c4dc1978486bb204ab29c7157c | |
parent | eeaacb6d6a0589ea9b969655e9624209913d5e47 (diff) | |
download | zope-interface-ab0466e56c6328407c3839a3a392cb127dbdb282.tar.gz |
Move Declaration, Specification and ClassProvides to __slots__.
In a test of 6000 modules that load 2245 InterfaceClass objects and produce 2233 ClassProvides instances, this saves about 1% total memory usage in Python 2.7.
-rw-r--r-- | src/zope/interface/_zope_interface_coptimizations.c | 34 | ||||
-rw-r--r-- | src/zope/interface/declarations.py | 22 | ||||
-rw-r--r-- | src/zope/interface/interface.py | 58 | ||||
-rw-r--r-- | src/zope/interface/tests/test_declarations.py | 14 | ||||
-rw-r--r-- | src/zope/interface/tests/test_interface.py | 2 |
5 files changed, 93 insertions, 37 deletions
diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c index db0b443..e308cbd 100644 --- a/src/zope/interface/_zope_interface_coptimizations.c +++ b/src/zope/interface/_zope_interface_coptimizations.c @@ -259,6 +259,7 @@ providedBy(PyObject *ignored, PyObject *ob) typedef struct { PyObject_HEAD + PyObject* weakreflist; /* In the past, these fields were stored in the __dict__ and were technically allowed to contain any Python object, though @@ -267,6 +268,15 @@ typedef struct { make any assumptions about contents. */ PyObject* _implied; + /* + The remainder aren't used in C code but must be stored here + to prevent instance layout conflicts. + */ + PyObject* dependents; + PyObject* _bases; + PyObject* _v_attrs; + PyObject* __iro__; + PyObject* __sro__; } Spec; /* @@ -277,6 +287,10 @@ static int Spec_traverse(Spec* self, visitproc visit, void* arg) { Py_VISIT(self->_implied); + Py_VISIT(self->dependents); + Py_VISIT(self->_v_attrs); + Py_VISIT(self->__iro__); + Py_VISIT(self->__sro__); return 0; } @@ -284,12 +298,19 @@ static int Spec_clear(Spec* self) { Py_CLEAR(self->_implied); + Py_CLEAR(self->dependents); + Py_CLEAR(self->_v_attrs); + Py_CLEAR(self->__iro__); + Py_CLEAR(self->__sro__); return 0; } static void Spec_dealloc(Spec* self) { + if (self->weakreflist != NULL) { + PyObject_ClearWeakRefs(OBJECT(self)); + } Spec_clear(self); Py_TYPE(self)->tp_free(OBJECT(self)); } @@ -387,7 +408,12 @@ static struct PyMethodDef Spec_methods[] = { static PyMemberDef Spec_members[] = { {"_implied", T_OBJECT_EX, offsetof(Spec, _implied), 0, ""}, - {NULL} + {"dependents", T_OBJECT_EX, offsetof(Spec, dependents), 0, ""}, + {"_bases", T_OBJECT_EX, offsetof(Spec, _bases), 0, ""}, + {"_v_attrs", T_OBJECT_EX, offsetof(Spec, _v_attrs), 0, ""}, + {"__iro__", T_OBJECT_EX, offsetof(Spec, __iro__), 0, ""}, + {"__sro__", T_OBJECT_EX, offsetof(Spec, __sro__), 0, ""}, + {NULL}, }; @@ -417,7 +443,7 @@ static PyTypeObject SpecType = { /* tp_traverse */ (traverseproc)Spec_traverse, /* tp_clear */ (inquiry)Spec_clear, /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, + /* tp_weaklistoffset */ offsetof(Spec, weakreflist), /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ Spec_methods, @@ -1779,3 +1805,7 @@ PyInit__zope_interface_coptimizations(void) return init(); } #endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py index 991fb95..cc20f5a 100644 --- a/src/zope/interface/declarations.py +++ b/src/zope/interface/declarations.py @@ -62,16 +62,11 @@ class named(object): class Declaration(Specification): """Interface declarations""" + __slots__ = () + def __init__(self, *interfaces): Specification.__init__(self, _normalizeargs(interfaces)) - def changed(self, originally_changed): - Specification.changed(self, originally_changed) - try: - del self._v_attrs - except AttributeError: - pass - def __contains__(self, interface): """Test whether an interface is in the specification """ @@ -625,9 +620,12 @@ def noLongerProvides(object, interface): @_use_c_impl -class ClassProvidesBase(object): - # In C, this extends SpecificationBase, so its kind of weird here that it - # doesn't. +class ClassProvidesBase(SpecificationBase): + + __slots__ = ( + '_cls', + '_implements', + ) def __get__(self, inst, cls): if cls is self._cls: @@ -916,6 +914,8 @@ def _normalizeargs(sequence, output=None): return output -_empty = Declaration() +# XXX: Declarations are mutable, allowing adjustments to their __bases__ +# so having one as a singleton may not be a great idea. +_empty = Declaration() # type: Declaration objectSpecificationDescriptor = ObjectSpecificationDescriptor() diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index 7a852f8..d3074c5 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -96,9 +96,27 @@ class Element(object): @_use_c_impl class SpecificationBase(object): - + # This object is the base of the inheritance hierarchy for ClassProvides: + # + # ClassProvides < ClassProvidesBase, Declaration + # Declaration < Specification < SpecificationBase + # ClassProvidesBase < SpecificationBase + # + # In order to have compatible instance layouts, we need to declare + # the storage used by Specification and Declaration here (and + # those classes must have ``__slots__ = ()``); fortunately this is + # not a waste of space because those are the only two inheritance + # trees. These all translate into tp_members in C. __slots__ = ( + # Things used here. '_implied', + # Things used in Specification. + 'dependents', + '_bases', + '_v_attrs', + '__iro__', + '__sro__', + '__weakref__', ) def providedBy(self, ob): @@ -128,6 +146,11 @@ class InterfaceBase(object): """Base class that wants to be replaced with a C base :) """ + __slots__ = () + + def _call_conform(self, conform): + raise NotImplementedError + def __call__(self, obj, alternate=_marker): """Adapt an object to the interface """ @@ -173,14 +196,20 @@ class Specification(SpecificationBase): Specifications are mutable. If you reassign their bases, their relations with other specifications are adjusted accordingly. """ + __slots__ = () # Copy some base class methods for speed isOrExtends = SpecificationBase.isOrExtends providedBy = SpecificationBase.providedBy def __init__(self, bases=()): - self._implied = {} self.dependents = weakref.WeakKeyDictionary() + self._bases = () + self._implied = {} + self._v_attrs = None + self.__iro__ = () + self.__sro__ = () + self.__bases__ = tuple(bases) def subscribe(self, dependent): @@ -201,25 +230,21 @@ class Specification(SpecificationBase): b.unsubscribe(self) # Register ourselves as a dependent of our bases - self.__dict__['__bases__'] = bases + self._bases = bases for b in bases: b.subscribe(self) self.changed(self) __bases__ = property( - - lambda self: self.__dict__.get('__bases__', ()), + lambda self: self._bases, __setBases, ) def changed(self, originally_changed): """We, or something we depend on, have changed """ - try: - del self._v_attrs - except AttributeError: - pass + self._v_attrs = None implied = self._implied implied.clear() @@ -245,6 +270,10 @@ class Specification(SpecificationBase): for dependent in tuple(self.dependents.keys()): dependent.changed(originally_changed) + # Just in case something called get() at some point + # during that process and we have a cycle of some sort + # make sure we didn't cache incomplete results. + self._v_attrs = None def interfaces(self): """Return an iterator for the interfaces in the specification. @@ -274,9 +303,8 @@ class Specification(SpecificationBase): def get(self, name, default=None): """Query for an attribute description """ - try: - attrs = self._v_attrs - except AttributeError: + attrs = self._v_attrs + if attrs is None: attrs = self._v_attrs = {} attr = attrs.get(name) if attr is None: @@ -286,10 +314,8 @@ class Specification(SpecificationBase): attrs[name] = attr break - if attr is None: - return default - else: - return attr + return default if attr is None else attr + class InterfaceClass(Element, InterfaceBase, Specification): """Prototype (scarecrow) Interfaces Implementation.""" diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py index 5b290f3..887a5cb 100644 --- a/src/zope/interface/tests/test_declarations.py +++ b/src/zope/interface/tests/test_declarations.py @@ -101,31 +101,31 @@ class DeclarationTests(unittest.TestCase): def test_changed_wo_existing__v_attrs(self): decl = self._makeOne() decl.changed(decl) # doesn't raise - self.assertFalse('_v_attrs' in decl.__dict__) + self.assertIsNone(decl._v_attrs) def test_changed_w_existing__v_attrs(self): decl = self._makeOne() decl._v_attrs = object() decl.changed(decl) - self.assertFalse('_v_attrs' in decl.__dict__) + self.assertIsNone(decl._v_attrs) def test___contains__w_self(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') decl = self._makeOne() - self.assertFalse(decl in decl) + self.assertNotIn(decl, decl) def test___contains__w_unrelated_iface(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') decl = self._makeOne() - self.assertFalse(IFoo in decl) + self.assertNotIn(IFoo, decl) def test___contains__w_base_interface(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') decl = self._makeOne(IFoo) - self.assertTrue(IFoo in decl) + self.assertIn(IFoo, decl) def test___iter___empty(self): decl = self._makeOne() @@ -454,7 +454,7 @@ class Test_implementedByFallback(unittest.TestCase): self.assertTrue(spec.inherit is foo) self.assertTrue(foo.__implemented__ is spec) self.assertTrue(foo.__providedBy__ is objectSpecificationDescriptor) - self.assertFalse('__provides__' in foo.__dict__) + self.assertNotIn('__provides__', foo.__dict__) def test_w_None_no_bases_w_class(self): from zope.interface.declarations import ClassProvides @@ -601,7 +601,7 @@ class Test__implements_advice(unittest.TestCase): class Foo(object): __implements_advice_data__ = ((IFoo,), classImplements) self._callFUT(Foo) - self.assertFalse('__implements_advice_data__' in Foo.__dict__) + self.assertNotIn('__implements_advice_data__', Foo.__dict__) self.assertIsInstance(Foo.__implemented__, Implements) self.assertEqual(list(Foo.__implemented__), [IFoo]) diff --git a/src/zope/interface/tests/test_interface.py b/src/zope/interface/tests/test_interface.py index 69b1d47..f5a57b4 100644 --- a/src/zope/interface/tests/test_interface.py +++ b/src/zope/interface/tests/test_interface.py @@ -370,7 +370,7 @@ class SpecificationTests(unittest.TestCase): spec._v_attrs = 'Foo' spec._implied[I] = () spec.changed(spec) - self.assertTrue(getattr(spec, '_v_attrs', self) is self) + self.assertIsNone(spec._v_attrs) self.assertFalse(I in spec._implied) def test_interfaces_skips_already_seen(self): |