[IMP] core: save memory of empty dicts

Use `frozendict` for dicts that often remain empty on models, like
_inherits and _depends, and also make `frozendict` more compact in
memory.

On a registry with 296 modules, this saves 635 kilobytes of memory,
which is about 6% of the registry's memory footprint.

closes odoo/odoo#70402

Related: odoo/enterprise#18142
Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
Raphael Collet
2021-05-10 14:29:43 +00:00
parent 4fb958d086
commit c6f1e4f0d7
2 changed files with 23 additions and 7 deletions
+13 -7
View File
@@ -472,7 +472,7 @@ class BaseModel(metaclass=MetaModel):
* If :attr:`._name` is set, name(s) of parent models to inherit from
* If :attr:`._name` is unset, name of a single model to extend in-place
"""
_inherits = {}
_inherits = frozendict()
"""dictionary {'parent_model': 'm2o_field'} mapping the _name of the parent business
objects to the names of the corresponding foreign key fields to use::
@@ -519,7 +519,7 @@ class BaseModel(metaclass=MetaModel):
as attribute.
"""
_depends = {}
_depends = frozendict()
"""dependencies of models backed up by SQL views
``{model_name: field_names}``, where ``field_names`` is an iterable.
This is only used to determine the changes to flush to database before
@@ -699,8 +699,8 @@ class BaseModel(metaclass=MetaModel):
cls._table = cls._name.replace('.', '_')
cls._sequence = None
cls._log_access = cls._auto
cls._inherits = {}
cls._depends = {}
inherits = {}
depends = {}
cls._sql_constraints = {}
for base in reversed(cls.__base_classes):
@@ -713,10 +713,10 @@ class BaseModel(metaclass=MetaModel):
cls._sequence = base._sequence or cls._sequence
cls._log_access = getattr(base, '_log_access', cls._log_access)
cls._inherits.update(base._inherits)
inherits.update(base._inherits)
for mname, fnames in base._depends.items():
cls._depends.setdefault(mname, []).extend(fnames)
depends.setdefault(mname, []).extend(fnames)
for cons in base._sql_constraints:
cls._sql_constraints[cons[0]] = cons
@@ -724,6 +724,12 @@ class BaseModel(metaclass=MetaModel):
cls._sequence = cls._sequence or (cls._table + '_id_seq')
cls._sql_constraints = list(cls._sql_constraints.values())
# avoid assigning an empty dict to save memory
if inherits:
cls._inherits = inherits
if depends:
cls._depends = depends
# update _inherits_children of parent models
for parent_name in cls._inherits:
pool[parent_name]._inherits_children.add(cls._name)
@@ -2841,7 +2847,7 @@ class BaseModel(metaclass=MetaModel):
field.required = True
if field.ondelete.lower() not in ('cascade', 'restrict'):
field.ondelete = 'cascade'
self._inherits[field.comodel_name] = field.name
type(self)._inherits = {**self._inherits, field.comodel_name: field.name}
self.pool[field.comodel_name]._inherits_children.add(self._name)
@api.model
+10
View File
@@ -940,22 +940,32 @@ def clean_context(context):
""" This function take a dictionary and remove each entry with its key starting with 'default_' """
return {k: v for k, v in context.items() if not k.startswith('default_')}
class frozendict(dict):
""" An implementation of an immutable dictionary. """
__slots__ = ()
def __delitem__(self, key):
raise NotImplementedError("'__delitem__' not supported on frozendict")
def __setitem__(self, key, val):
raise NotImplementedError("'__setitem__' not supported on frozendict")
def clear(self):
raise NotImplementedError("'clear' not supported on frozendict")
def pop(self, key, default=None):
raise NotImplementedError("'pop' not supported on frozendict")
def popitem(self):
raise NotImplementedError("'popitem' not supported on frozendict")
def setdefault(self, key, default=None):
raise NotImplementedError("'setdefault' not supported on frozendict")
def update(self, *args, **kwargs):
raise NotImplementedError("'update' not supported on frozendict")
def __hash__(self):
return hash(frozenset((key, freehash(val)) for key, val in self.items()))