Commits

Kirill Simonov  committed 2f71efd

Reduce memory footprint by using `__slots__` on model and entity objects.

  • Participants
  • Parent commits f914f66

Comments (0)

Files changed (5)

File src/htsql/core/domain.py

     to the other.
     """
 
+    __slots__ = ()
+
     # Make sure `str(domain.__class__)` produces a usable domain family name.
     class __metaclass__(type):
 
     This is an abstract class.
     """
 
+    __slots__ = ()
+
 
 class VoidDomain(NullDomain):
     """
     but has no semantics.
     """
 
+    __slots__ = ()
+
 
 class EntityDomain(NullDomain):
     """
     this domain does not support any values.
     """
 
+    __slots__ = ()
+
 
 #
 # Scalar domains.
     domain could be derived from the context.
     """
 
+    __slots__ = ()
+
     @staticmethod
     def parse(text):
         # Sanity check on the argument.
     Valid native objects: ``bool`` values.
     """
 
+    __slots__ = ()
+
     @staticmethod
     def parse(text):
         assert isinstance(text, maybe(unicode))
         Indicates whether the values are stored in binary or decimal form.
     """
 
+    __slots__ = ()
+
     is_exact = None
     radix = None
 
         Number of bits used to store a value; ``None`` if not known.
     """
 
+    __slots__ = ('size',)
+
     is_exact = True
     radix = 2
 
         Number of bits used to store a value; ``None`` if not known.
     """
 
+    __slots__ = ('size',)
+
     is_exact = False
     radix = 2
 
         ``None`` if infinite or not known.
     """
 
+    __slots__ = ('precision', 'scale')
+
     is_exact = True
     radix = 10
 
         Indicates whether values are fixed-length or variable-length.
     """
 
+    __slots__ = ('length', 'is_varying')
+
     def __init__(self, length=None, is_varying=True):
         assert isinstance(length, maybe(int))
         assert isinstance(is_varying, bool)
         List of valid values.
     """
 
+    __slots__ = ('labels',)
+
     # NOTE: HTSQL enum type is structural, but some SQL databases implement
     # enums as nominal types (e.g. PostgreSQL).  In practice, it should not be
     # a problem since it is unlikely that two nominally different enum types
     Valid native objects: ``datetime.date`` values.
     """
 
+    __slots__ = ()
+
     # Regular expression to match YYYY-MM-DD.
     regexp = re.compile(r'''(?x)
         ^ \s*
     Valid native objects: ``datetime.time`` values.
     """
 
+    __slots__ = ()
+
     # Regular expression to match HH:MM:SS.SSSSSS.
     regexp = re.compile(r'''(?x)
         ^ \s*
     Valid native objects: ``datetime.datetime`` values.
     """
 
+    __slots__ = ()
+
     # Regular expression to match YYYY-MM-DD HH:MM:SS.SSSSSS.
     regexp = re.compile(r'''(?x)
         ^ \s*
     Valid native objects: any.
     """
 
+    __slots__ = ()
+
     @staticmethod
     def parse(text):
         assert isinstance(text, maybe(unicode))
     This is an abstract superclass for container domains.
     """
 
+    __slots__ = ()
+
     @staticmethod
     def parse_entry(text, domain):
         # Unquotes and parses a container entry.
         The type of entries.
     """
 
+    __slots__ = ('item_domain',)
+
     def __init__(self, item_domain):
         assert isinstance(item_domain, Domain)
         self.item_domain = item_domain
         The types and other structural metadata of the record fields.
     """
 
+    __slots__ = ('fields',)
+
     def __init__(self, fields):
         assert isinstance(fields, listof(Profile))
         self.fields = fields
         Paths (as tuple indexes) to leaf labels.
     """
 
+    __slots__ = ('labels', 'width', 'leaves')
+
     def __init__(self, labels):
         assert isinstance(labels, listof(Domain))
         self.labels = labels

File src/htsql/core/entity.py

 
 class Entity(Printable):
 
+    __slots__ = ('owner',)
+
     is_frozen = True
 
     def __init__(self, owner):
 
 class MutableEntity(Entity):
 
+    __slots__ = ()
+
     is_frozen = False
 
     def remove(self):
-        self.__dict__.clear()
-        self.__class__ = RemovedEntity
+        for cls in self.__class__.__mro__:
+            if hasattr(cls, '__slots__'):
+                for slot in cls.__slots__:
+                    if not (slot.startswith('__') and slot.endswith('__')):
+                        delattr(self, slot)
 
 
 class NamedEntity(Entity):
 
+    __slots__ = ('name',)
+
     def __init__(self, owner, name):
         assert isinstance(name, unicode)
         super(NamedEntity, self).__init__(owner)
             return u"<default>"
 
 
-class RemovedEntity(Printable):
+class EntitySet(Printable):
 
-    def __unicode__(self):
-        return u"<removed>"
-
-    def __str__(self):
-        return "<removed>"
-
-
-class EntitySet(Printable):
+    __slots__ = ('entities', 'index_by_name')
 
     is_frozen = True
 
 
 class MutableEntitySet(EntitySet):
 
+    __slots__ = ()
+
     is_frozen = False
 
     def add(self, entity):
 
 
 class CatalogEntity(Entity):
-    pass
+
+    __slots__ = ('schemas', '__weakref__')
 
 
 class MutableCatalogEntity(CatalogEntity, MutableEntity):
 
+    __slots__ = ()
+
     def __init__(self):
         super(MutableCatalogEntity, self).__init__(weakref.ref(self))
         self.schemas = MutableEntitySet()
     def remove(self):
         for schema in reversed(self.schemas):
             schema.remove()
-        self.__dict__.clear()
-        self.__class__ = RemovedEntity
+        super(MutableCatalogEntity, self).remove()
 
 
 class SchemaEntity(NamedEntity):
 
+    __slots__ = ('tables', 'priority', '__weakref__')
+
     @property
     def catalog(self):
         return self.owner()
 
 class MutableSchemaEntity(SchemaEntity, MutableEntity):
 
+    __slots__ = ()
+
     def __init__(self, catalog, name, priority):
         assert isinstance(catalog, MutableCatalogEntity)
         assert name not in catalog.schemas
         for table in reversed(list(self.tables)):
             table.remove()
         self.catalog.schemas.remove(self)
-        self.__dict__.clear()
-        self.__class__ = RemovedEntity
+        super(MutableSchemaEntity, self).remove()
 
 
 class TableEntity(NamedEntity):
 
+    __slots__ = ('columns', 'primary_key', 'unique_keys',
+                 'foreign_keys', 'referring_foreign_keys', '__weakref__')
+
     @property
     def schema(self):
         return self.owner()
 
 class MutableTableEntity(TableEntity, MutableEntity):
 
+    __slots__ = ()
+
     def __init__(self, schema, name):
         assert isinstance(schema, MutableSchemaEntity)
         assert name not in schema.tables
         for column in reversed(list(self.columns)):
             column.remove()
         self.schema.tables.remove(self)
-        self.__dict__.clear()
-        self.__class__ = RemovedEntity
+        super(MutableTableEntity, self).remove()
 
 
 class ColumnEntity(NamedEntity, MutableEntity):
 
+    __slots__ = ('domain', 'is_nullable', 'has_default')
+
     @property
     def table(self):
         return self.owner()
 
 class MutableColumnEntity(ColumnEntity, MutableEntity):
 
+    __slots__ = ()
+
     def __init__(self, table, name, domain, is_nullable, has_default):
         assert isinstance(table, MutableTableEntity)
         assert name not in table.columns
         for foreign_key in self.referring_foreign_keys:
             foreign_key.remove()
         self.table.columns.remove(self)
-        self.__dict__.clear()
-        self.__class__ = RemovedEntity
+        super(MutableColumnEntity, self).remove()
 
 
 class UniqueKeyEntity(Entity):
 
+    __slots__ = ('origin_columns', 'is_primary', 'is_partial')
+
     @property
     def origin(self):
         return self.owner()
 
 class MutableUniqueKeyEntity(UniqueKeyEntity, MutableEntity):
 
+    __slots__ = ()
+
     def __init__(self, origin, origin_columns, is_primary, is_partial):
         assert isinstance(origin, MutableTableEntity)
         assert isinstance(origin_columns, listof(MutableColumnEntity))
         self.origin.unique_keys.remove(self)
         if self.is_primary:
             self.origin.primary_key = None
-        self.__dict__.clear()
-        self.__class__ = RemovedEntity
+        super(MutableUniqueKeyEntity, self).remove()
 
 
 class ForeignKeyEntity(Entity):
 
+    __slots__ = ('origin_columns', 'coowner', 'target_columns', 'is_partial')
+
     @property
     def origin(self):
         return self.owner()
 
 class MutableForeignKeyEntity(ForeignKeyEntity, MutableEntity):
 
+    __slots__ = ()
+
     def __init__(self, origin, origin_columns, target, target_columns,
                  is_partial):
         assert isinstance(origin, MutableTableEntity)
     def remove(self):
         self.origin.foreign_keys.remove(self)
         self.target.referring_foreign_keys.remove(self)
-        self.__dict__.clear()
-        self.__class__ = RemovedEntity
+        super(MutableForeignKeyEntity, self).remove()
 
 
 class Join(Printable, Hashable):
     """
     # FIXME: do joins belong to `entity.py`?
 
+    __slots__ = ('origin', 'target', 'origin_columns', 'target_columns',
+                 'is_expanding', 'is_contracting')
+
     is_direct = False
     is_reverse = False
 
         The foreign key that generates the join condition.
     """
 
+    __slots__ = ('foreign_key',)
+
     is_direct = True
 
     def __init__(self, foreign_key):
         The foreign key that generates the join condition.
     """
 
+    __slots__ = ('foreign_key',)
+
     is_reverse = True
 
     def __init__(self, foreign_key):

File src/htsql/core/model.py

 
 class Model(Hashable, Clonable, Printable):
 
+    __slots__ = ()
+
     def __init__(self):
         pass
 
 
 class Node(Model):
-    pass
+
+    __slots__ = ()
 
 
 class Arc(Model):
 
+    __slots__ = ('origin', 'target', 'arity', 'is_expanding', 'is_contracting')
+
     def __init__(self, origin, target, arity, is_expanding, is_contracting):
         assert isinstance(origin, Node)
         assert isinstance(target, Node)
 
 class Label(Clonable, Printable):
 
+    __slots__ = ('name', 'arc', 'origin', 'target', 'arity',
+                 'is_expanding', 'is_contracting', 'is_public')
+
     def __init__(self, name, arc, is_public):
         assert isinstance(name, unicode)
         assert isinstance(arc, Arc)
 
 class HomeNode(Node):
 
+    __slots__ = ()
+
     def __basis__(self):
         return ()
 
 
 class TableNode(Node):
 
+    __slots__ = ('table',)
+
     def __init__(self, table):
         assert isinstance(table, TableEntity)
         self.table = table
 
 class DomainNode(Node):
 
+    __slots__ = ('domain',)
+
     def __init__(self, domain):
         assert isinstance(domain, Domain)
         self.domain = domain
 
 class UnknownNode(Node):
 
+    __slots__ = ()
+
     def __basis__(self):
         return ()
 
 
 class InvalidNode(Node):
 
+    __slots__ = ()
+
     def __basis__(self):
         return ()
 
 
 class TableArc(Arc):
 
+    __slots__ = ('table',)
+
     def __init__(self, table):
         assert isinstance(table, TableEntity)
         super(TableArc, self).__init__(
 
 class ChainArc(Arc):
 
+    __slots__ = ('table', 'joins', 'is_direct', 'is_reverse')
+
     def __init__(self, table, joins):
         assert isinstance(table, TableEntity)
         assert isinstance(joins, listof(Join)) and len(joins) > 0
 
 class ColumnArc(Arc):
 
+    __slots__ = ('table', 'column', 'link')
+
     def __init__(self, table, column, link=None):
         assert isinstance(table, TableEntity)
         assert isinstance(column, ColumnEntity) and column.table is table
 
 class SyntaxArc(Arc):
 
+    __slots__ = ('parameters', 'syntax')
+
     def __init__(self, origin, parameters, syntax):
         assert isinstance(parameters, maybe(listof(tupleof(unicode, bool))))
         assert isinstance(syntax, Syntax)
 
 class InvalidArc(Arc):
 
+    __slots__ = ()
+
     def __init__(self, origin, arity):
         assert isinstance(origin, Node)
         super(InvalidArc, self).__init__(
 
 class AmbiguousArc(InvalidArc):
 
+    __slots__ = ('alternatives',)
+
     def __init__(self, arity, alternatives):
         assert isinstance(alternatives, listof(Arc)) and len(alternatives) > 0
         origin = alternatives[0].origin

File src/htsql/core/tr/flow.py

         # is a tuple of all its essential attributes and the first element
         # of the tuple is the flow base.  So we skip the base flow and
         # compare the remaining attributes.
-        return (isinstance(other, self.__class__) and
-                self._basis[1:] == other._basis[1:])
+        if not isinstance(other, self.__class__):
+            return False
+        try:
+            _basis = self._basis
+        except AttributeError:
+            self._rehash()
+            _basis = self._basis
+        try:
+            _other_basis = other._basis
+        except AttributeError:
+            other._rehash()
+            _other_basis = other._basis
+        return (_basis[1:] == _other_basis[1:])
 
     def inflate(self):
         """

File src/htsql/core/util.py

         attributes.
     """
 
+    __slots__ = ()
+
     def __init__(self):
         # Must be overriden in subclasses.
         raise NotImplementedError()
     the same type and their basis vectors are equal.
     """
 
+    __slots__ = ('_basis', '_hash')
+
     def __hash__(self):
-        return self._hash
+        try:
+            return self._hash
+        except AttributeError:
+            self._rehash()
+            return self._hash
 
     def __basis__(self):
         """
         Returns a vector of values uniquely identifying the object.
         """
         raise NotImplementedError()
-        ## By default, objects are compared by identity.  Reimplement
-        ## for by-value comparison.
-        #return id(self)
 
-    @cachedproperty
-    def _hash(self):
-        # Calculate and cache the object hash.
-        return hash(self._basis)
-
-    @cachedproperty
-    def _basis(self):
-        # Get the object basis vector.
+    def _rehash(self):
+        # Calculate the object hash and the basis vector.
         _basis = self.__basis__()
         # Flatten and return the vector.
         if isinstance(_basis, tuple):
             elements = []
             for element in _basis:
                 if isinstance(element, Hashable):
-                    elements.append((element.__class__,
-                                     element._hash,
-                                     element._basis))
+                    element_class = element.__class__
+                    try:
+                        element_hash = element._hash
+                        element_basis = element._basis
+                    except AttributeError:
+                        element._rehash()
+                        element_hash = element._hash
+                        element_basis = element._basis
+                    elements.append((element_class,
+                                     element_hash,
+                                     element_basis))
                 else:
                     elements.append(element)
             _basis = tuple(elements)
-        return _basis
+        self._basis = _basis
+        self._hash = hash(_basis)
 
     def __eq__(self, other):
         # We could just compare object basis vectors, but
         # for performance, we start with faster checks.
-        return ((self is other) or
-                (isinstance(other, Hashable) and
-                 self.__class__ is other.__class__ and
-                 self._hash == other._hash and
-                 self._basis == other._basis))
+        if self is other:
+            return True
+        if not (isinstance(other, Hashable) and
+                self.__class__ is other.__class__):
+            return False
+        try:
+            _hash = self._hash
+            _basis = self._basis
+        except AttributeError:
+            self._rehash()
+            _hash = self._hash
+            _basis = self._basis
+        try:
+            _other_hash = other._hash
+            _other_basis = other._basis
+        except AttributeError:
+            other._rehash()
+            _other_hash = other._hash
+            _other_basis = other._basis
+        return (_hash == _other_hash and _basis == _other_basis)
 
     def __ne__(self, other):
         # Since we override `==`, we also need to override `!=`.
-        return ((self is not other) and
-                (not isinstance(other, Hashable) or
-                 self.__class__ is not other.__class__ or
-                 self._hash != other._hash and
-                 self._basis != other._basis))
+        if self is other:
+            return False
+        return not (self == other)
 
 
 class Printable(object):
     :meth:`__unicode__` method.
     """
 
+    __slots__ = ()
+
     def __unicode__(self):
         # Override in subclasses.
         return u"-"