[IMP] fields: reuse column objects when possible, instead of recreating them

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.
This commit is contained in:
Raphael Collet 2014-10-22 12:49:12 +02:00
parent 575ea15518
commit 8e6d5beb35
2 changed files with 31 additions and 13 deletions

View 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):

View 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',):