[IMP] api: move cache_key to the Environment and cache it
Computing the `cache_key` turned out to be a big factor during the lifespan of a `BaseModel.mapped` call and a lot of this time is spent computing the same `cache_key` over and over. These unnecessary computations can be easily reduced to a couple by moving the `cache_key` method on the environment (instead of the field) and by implementing a memo for that method. The rationale is that the `cache_key` of a field does not change for a given environment. The result of this patch is up to 50% faster `Field.__get__` which in turn means a GLOBAL gain in performance, especially for methods / functions that rely heavily on `__get__` such as `BaseModel.mapped`. closes odoo/odoo#42674 Signed-off-by: Adrian Torres (adt) <adt@odoo.com>
This commit is contained in:
committed by
Raphael Collet
parent
8645e31863
commit
be01db2f71
+38
-7
@@ -452,6 +452,7 @@ class Environment(Mapping):
|
||||
self.cr, self.uid, self.context, self.su = self.args = args
|
||||
self.registry = Registry(cr.dbname)
|
||||
self.cache = envs.cache
|
||||
self._cache_key = {} # memo {field: cache_key}
|
||||
self._protected = envs.protected # proxy to shared data structure
|
||||
self.all = envs
|
||||
envs.add(self)
|
||||
@@ -709,6 +710,36 @@ class Environment(Mapping):
|
||||
""" Delay recomputations (deprecated: this is not the default behavior). """
|
||||
yield
|
||||
|
||||
def cache_key(self, field):
|
||||
""" Return the cache key corresponding to ``field.depends_context``. """
|
||||
try:
|
||||
return self._cache_key[field]
|
||||
|
||||
except KeyError:
|
||||
def get(key, get_context=self.context.get):
|
||||
if key == 'company':
|
||||
return self.company.id
|
||||
elif key == 'uid':
|
||||
return (self.uid, self.su)
|
||||
elif key == 'active_test':
|
||||
return get_context('active_test', field.context.get('active_test', True))
|
||||
else:
|
||||
val = get_context(key)
|
||||
try:
|
||||
hash(val)
|
||||
except TypeError:
|
||||
raise TypeError(
|
||||
"Can only create cache keys from hashable values, "
|
||||
"got non-hashable value {!r} at context key {!r} "
|
||||
"(dependency of field {})".format(val, key, field)
|
||||
) from None # we don't need to chain the exception created 2 lines above
|
||||
else:
|
||||
return val
|
||||
|
||||
result = tuple(get(key) for key in field.depends_context)
|
||||
self._cache_key[field] = result
|
||||
return result
|
||||
|
||||
|
||||
class Environments(object):
|
||||
""" A common object for all environments in a request. """
|
||||
@@ -742,7 +773,7 @@ class Cache(object):
|
||||
def contains(self, record, field):
|
||||
""" Return whether ``record`` has a value for ``field``. """
|
||||
if field.depends_context:
|
||||
key = field.cache_key(record.env)
|
||||
key = record.env.cache_key(field)
|
||||
return key in self._data.get(field, {}).get(record.id, {})
|
||||
return record.id in self._data.get(field, ())
|
||||
|
||||
@@ -751,7 +782,7 @@ class Cache(object):
|
||||
try:
|
||||
value = self._data[field][record._ids[0]]
|
||||
if field.depends_context:
|
||||
value = value[field.cache_key(record.env)]
|
||||
value = value[record.env.cache_key(field)]
|
||||
return value
|
||||
except KeyError:
|
||||
if default is NOTHING:
|
||||
@@ -761,7 +792,7 @@ class Cache(object):
|
||||
def set(self, record, field, value):
|
||||
""" Set the value of ``field`` for ``record``. """
|
||||
if field.depends_context:
|
||||
key = field.cache_key(record.env)
|
||||
key = record.env.cache_key(field)
|
||||
self._data[field].setdefault(record._ids[0], {})[key] = value
|
||||
else:
|
||||
self._data[field][record._ids[0]] = value
|
||||
@@ -769,7 +800,7 @@ class Cache(object):
|
||||
def update(self, records, field, values):
|
||||
""" Set the values of ``field`` for several ``records``. """
|
||||
if field.depends_context:
|
||||
key = field.cache_key(records.env)
|
||||
key = records.env.cache_key(field)
|
||||
field_cache = self._data[field]
|
||||
for record_id, value in zip(records._ids, values):
|
||||
field_cache.setdefault(record_id, {})[key] = value
|
||||
@@ -786,7 +817,7 @@ class Cache(object):
|
||||
def get_values(self, records, field):
|
||||
""" Return the cached values of ``field`` for ``records``. """
|
||||
field_cache = self._data[field]
|
||||
key = field.cache_key(records.env) if field.depends_context else None
|
||||
key = records.env.cache_key(field) if field.depends_context else None
|
||||
for record_id in records._ids:
|
||||
try:
|
||||
if key is not None:
|
||||
@@ -799,7 +830,7 @@ class Cache(object):
|
||||
def get_records_different_from(self, records, field, value):
|
||||
""" Return the subset of ``records`` that has not ``value`` for ``field``. """
|
||||
field_cache = self._data[field]
|
||||
key = field.cache_key(records.env) if field.depends_context else None
|
||||
key = records.env.cache_key(field) if field.depends_context else None
|
||||
ids = []
|
||||
for record_id in records._ids:
|
||||
try:
|
||||
@@ -822,7 +853,7 @@ class Cache(object):
|
||||
values = self._data.get(field, {})
|
||||
if record.id not in values:
|
||||
continue
|
||||
if field.depends_context and field.cache_key(record.env) not in values[record.id]:
|
||||
if field.depends_context and record.env.cache_key(field) not in values[record.id]:
|
||||
continue
|
||||
yield field
|
||||
|
||||
|
||||
@@ -632,34 +632,6 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
Property = records.env['ir.property']
|
||||
return Property.search_multi(self.name, self.model_name, operator, value)
|
||||
|
||||
#
|
||||
# Cache key for context-dependent fields
|
||||
#
|
||||
|
||||
def cache_key(self, env):
|
||||
""" Return the cache key corresponding to ``self.depends_context``. """
|
||||
|
||||
def get(key, get_context=env.context.get):
|
||||
if key == 'company':
|
||||
return env.company.id
|
||||
elif key == 'uid':
|
||||
return (env.uid, env.su)
|
||||
elif key == 'active_test':
|
||||
return get_context('active_test', self.context.get('active_test', True))
|
||||
else:
|
||||
v = get_context(key)
|
||||
try: hash(v)
|
||||
except TypeError:
|
||||
raise TypeError(
|
||||
"Can only create cache keys from hashable values, "
|
||||
"got non-hashable value {!r} at context key {!r} "
|
||||
"(dependency of field {})".format(v, key, self)
|
||||
) from None # we don't need to chain the exception created 2 lines above
|
||||
else:
|
||||
return v
|
||||
|
||||
return tuple(get(key) for key in self.depends_context)
|
||||
|
||||
#
|
||||
# Setup of field triggers
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user