[IMP] fields: reuse column objects when possible, instead of recreating them
authorRaphael Collet <rco@openerp.com>
Wed, 22 Oct 2014 10:49:12 +0000 (12:49 +0200)
committerRaphael Collet <rco@openerp.com>
Wed, 22 Oct 2014 14:22:39 +0000 (16:22 +0200)
This is a memory optimization: it reduces the memory footprint of each
registry.  We have observed a reduction of 10Mb on a database with modules crm,
sale, purchase, stock.

openerp/fields.py
openerp/osv/fields.py

index 6081a61..ba3c5be 100644 (file)
@@ -629,8 +629,7 @@ class Field(object):
         """ return a low-level field object corresponding to `self` """
         assert self.store
 
-        # some columns are registry-dependent, like float fields (digits);
-        # duplicate them to avoid sharing between registries
+        # determine column parameters
         _logger.debug("Create fields._column for Field %s", self)
         args = {}
         for attr, prop in self.column_attrs:
@@ -644,10 +643,9 @@ class Field(object):
             args['relation'] = self.comodel_name
             return fields.property(**args)
 
-        if isinstance(self.column, fields.function):
-            # it is too tricky to recreate a function field, so for that case,
-            # we make a stupid (and possibly incorrect) copy of the column
-            return copy(self.column)
+        if self.column:
+            # let the column provide a valid column for the given parameters
+            return self.column.new(**args)
 
         return getattr(fields, self.type)(**args)
 
@@ -1303,14 +1301,14 @@ class Selection(Field):
 
 class Reference(Selection):
     type = 'reference'
-    size = 128
+    size = None
 
     def __init__(self, selection=None, string=None, **kwargs):
         super(Reference, self).__init__(selection=selection, string=string, **kwargs)
 
     def _setup(self, env):
         super(Reference, self)._setup(env)
-        assert isinstance(self.size, int), \
+        assert isinstance(self.size, (NoneType, int)), \
             "Reference field %s with non-integer size %r" % (self, self.size)
 
     _related_size = property(attrgetter('size'))
@@ -1725,10 +1723,6 @@ class Id(Field):
         super(Id, self).__init__(type='integer', string=string, **kwargs)
 
     def to_column(self):
-        """ to_column() -> fields._column
-
-        Whatever
-        """
         return fields.integer('ID')
 
     def __get__(self, record, owner):
index 9ae4114..30fe1f9 100644 (file)
@@ -104,7 +104,7 @@ class _column(object):
         self.help = args.get('help', '')
         self.priority = priority
         self.change_default = change_default
-        self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
+        self.ondelete = ondelete.lower() if ondelete else 'set null'
         self.translate = translate
         self._domain = domain
         self._context = context
@@ -125,6 +125,21 @@ class _column(object):
         if not self._classic_write or self.deprecated:
             self._prefetch = False
 
+    def new(self, **args):
+        """ return a column like `self` with the given parameters """
+        # memory optimization: reuse self whenever possible; you can reduce the
+        # average memory usage per registry by 10 megabytes!
+        return self if self.same_parameters(args) else type(self)(**args)
+
+    def same_parameters(self, args):
+        dummy = object()
+        return all(
+            # either both are falsy, or they are equal
+            (not val1 and not val) or (val1 == val)
+            for key, val in args.iteritems()
+            for val1 in [getattr(self, key, getattr(self, '_' + key, dummy))]
+        )
+
     def to_field(self):
         """ convert column `self` to a new-style field """
         from openerp.fields import Field
@@ -318,6 +333,10 @@ class float(_column):
         # synopsis: digits_compute(cr) ->  (precision, scale)
         self.digits_compute = digits_compute
 
+    def new(self, **args):
+        # float columns are database-dependent, so always recreate them
+        return type(self)(**args)
+
     def to_field_args(self):
         args = super(float, self).to_field_args()
         args['digits'] = self.digits_compute or self.digits
@@ -1248,6 +1267,11 @@ class function(_column):
                 self._symbol_f = type_class._symbol_f
                 self._symbol_set = type_class._symbol_set
 
+    def new(self, **args):
+        # HACK: function fields are tricky to recreate, simply return a copy
+        import copy
+        return copy.copy(self)
+
     def to_field_args(self):
         args = super(function, self).to_field_args()
         if self._type in ('float',):