From ffa7f28d34a449d65ef353403cc2d2a12e256d93 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 11 Mar 2015 10:36:17 +0100 Subject: [PATCH] [IMP] fields: reduce memory footprint of list/set field attributes The optimization consists in using tuples for attributes `inverse_fields`, `computed_fields` and `_triggers`, and to let them share their value when it is empty, which is common. This saves around 1.8Mb per registry. --- openerp/fields.py | 34 ++++++++++++++++++++-------------- openerp/models.py | 15 ++++++++------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/openerp/fields.py b/openerp/fields.py index 98e5e3f4a1f..3167819534a 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -268,7 +268,6 @@ class Field(object): relational = False # whether the field is a relational one model_name = None # name of the model of this field comodel_name = None # name of the model of values (if relational) - inverse_fields = None # list of inverse fields (objects) store = True # whether the field is stored in database index = False # whether the field is indexed in database @@ -294,18 +293,15 @@ class Field(object): change_default = None # whether the field may trigger a "user-onchange" deprecated = None # whether the field is ... deprecated + inverse_fields = () # collection of inverse fields (objects) + computed_fields = () # fields computed with the same method as self + _triggers = () # invalidation and recomputation triggers + def __init__(self, string=None, **kwargs): kwargs['string'] = string attrs = {key: val for key, val in kwargs.iteritems() if val is not None} self._attrs = attrs or EMPTY_DICT - # self._triggers is a set of pairs (field, path) that represents the - # computed fields that depend on `self`. When `self` is modified, it - # invalidates the cache of each `field`, and registers the records to - # recompute based on `path`. See method `modified` below for details. - self._triggers = set() - self.inverse_fields = [] - def _set_attr(self, name, value): """ Set the given field attribute, and add it to `_attrs` if necessary. """ object.__setattr__(self, name, value) @@ -542,6 +538,16 @@ class Field(object): # # Setup of field triggers # + # The triggers is a collection of pairs (field, path) of computed fields + # that depend on `self`. When `self` is modified, it invalidates the cache + # of each `field`, and registers the records to recompute based on `path`. + # See method `modified` below for details. + # + + def add_trigger(self, trigger): + """ Add a recomputation trigger on `self`. """ + if trigger not in self._triggers: + self._triggers += (trigger,) def setup_triggers(self, env): """ Add the necessary triggers to invalidate/recompute `self`. """ @@ -570,12 +576,12 @@ class Field(object): continue #_logger.debug("Add trigger on %s to recompute %s", field, self) - field._triggers.add((self, '.'.join(path0 or ['id']))) + field.add_trigger((self, '.'.join(path0 or ['id']))) # add trigger on inverse fields, too for invf in field.inverse_fields: #_logger.debug("Add trigger on %s to recompute %s", invf, self) - invf._triggers.add((self, '.'.join(path0 + [head]))) + invf.add_trigger((self, '.'.join(path0 + [head]))) # recursively traverse the dependency if tail: @@ -1684,8 +1690,8 @@ class One2many(_RelationalMulti): # (res_model/res_id pattern). Only inverse the field if this is # a `Many2one` field. if isinstance(invf, Many2one): - self.inverse_fields.append(invf) - invf.inverse_fields.append(self) + self.inverse_fields += (invf,) + invf.inverse_fields += (self,) _description_relation_field = property(attrgetter('inverse_name')) @@ -1756,8 +1762,8 @@ class Many2many(_RelationalMulti): # if inverse field has already been setup, it is present in m2m invf = m2m.get((self.relation, self.column2, self.column1)) if invf: - self.inverse_fields.append(invf) - invf.inverse_fields.append(self) + self.inverse_fields += (invf,) + invf.inverse_fields += (self,) else: # add self in m2m, so that its inverse field can find it m2m[(self.relation, self.column1, self.column2)] = self diff --git a/openerp/models.py b/openerp/models.py index 0846521888d..d9f565cb781 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -2992,14 +2992,15 @@ class BaseModel(object): if column: cls._columns[name] = column - # group fields by compute to determine field.computed_fields - fields_by_compute = defaultdict(list) + # determine field.computed_fields + computed_fields = defaultdict(list) for field in cls._fields.itervalues(): if field.compute: - field.computed_fields = fields_by_compute[field.compute] - field.computed_fields.append(field) - else: - field.computed_fields = [] + computed_fields[field.compute].append(field) + + for fields in computed_fields.itervalues(): + for field in fields: + field.computed_fields = fields @api.model def _setup_complete(self): @@ -3017,7 +3018,7 @@ class BaseModel(object): model = self.env[model_name] for field_name in field_names: field = model._fields[field_name] - field._triggers.update(triggers) + map(field.add_trigger, triggers) # determine old-api structures about inherited fields cls._inherits_reload()