summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Torres <adt@odoo.com>2020-01-03 08:12:03 +0000
committerRaphael Collet <rco@odoo.com>2020-01-21 08:28:36 +0000
commitbe01db2f712952a6c1d3688c8587d9624ef5dcc5 (patch)
treea152b7fbb78b6367dd129b72b3fe08bc1632169c
parent8645e31863fa76a737ce5cf79a41c95ce7853507 (diff)
[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>
-rw-r--r--odoo/api.py45
-rw-r--r--odoo/fields.py28
2 files changed, 38 insertions, 35 deletions
diff --git a/odoo/api.py b/odoo/api.py
index 68b92943640..cf56718b032 100644
--- a/odoo/api.py
+++ b/odoo/api.py
@@ -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
diff --git a/odoo/fields.py b/odoo/fields.py
index 449544e2250..3982f9c00da 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -633,34 +633,6 @@ class Field(MetaField('DummyField', (object,), {})):
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
#