From a6f8de2cf275c5346fc7c7ecf1d1d737db8baeb2 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Tue, 2 May 2023 18:38:35 +0000 Subject: [PATCH] [IMP] base: speedup routing map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When generating the routing map, `Rule.add` will call `Rule._compile_builder` twice, representing almost one third of the routing map generation time. `_compile_builder` is actually stored in `Rule._build` and `Rule._build_unknown` for latter use, in the `Rule.build` method. The `Rule.build` method is used to transform a rule, lets say `/forum/` in `/forum/basics-of-gardening-2` Even if a deeper investigation could be interresting, it looks like it is only used for `is_frontend_multilang` routes and `_enumerate_pages`, used in the `sitemap` and `search_pages`. The proposed solution si to make this part lazy, in order to call _compile_builder on demand, once per rule. This will speedup the initial routing map generation and postpone the heavy work when we need it, only for the part we need most of the time. On a database with all modules installed, generation of the routing map: Before: ~600 ms After: ~200 ms An alternative implementation was also overriding the Rule.build method instead of having a callable LazyCompiledBuilder, this implementation was choosen since it is less dependant off the werkzeug implementation. closes odoo/odoo#120542 Signed-off-by: Xavier Dollé (xdo) --- odoo/addons/base/models/ir_http.py | 38 +++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py index 1dbb94cca5b..3eba7932a44 100644 --- a/odoo/addons/base/models/ir_http.py +++ b/odoo/addons/base/models/ir_http.py @@ -79,6 +79,42 @@ class SignedIntConverter(NumberConverter): num_convert = int +class LazyCompiledBuilder: + def __init__(self, rule, _compile_builder, append_unknown): + self.rule = rule + self._callable = None + self._compile_builder = _compile_builder + self._append_unknown = append_unknown + + def __get__(self, *args): + # Rule.compile will actually call + # + # self._build = self._compile_builder(False).__get__(self, None) + # self._build_unknown = self._compile_builder(True).__get__(self, None) + # + # meaning the _build and _build unkown will contain _compile_builder().__get__(self, None). + # This is why this override of __get__ is needed. + return self + + def __call__(self, *args, **kwargs): + if self._callable is None: + self._callable = self._compile_builder(self._append_unknown).__get__(self.rule, None) + del self.rule + del self._compile_builder + del self._append_unknown + return self._callable(*args, **kwargs) + + +class FasterRule(werkzeug.routing.Rule): + """ + _compile_builder is a major part of the routing map generation and rules + are actually not build so often. + This classe makes calls to _compile_builder lazy + """ + def _compile_builder(self, append_unknown=True): + return LazyCompiledBuilder(self, super()._compile_builder, append_unknown) + + class IrHttp(models.AbstractModel): _name = 'ir.http' _description = "HTTP Routing" @@ -206,7 +242,7 @@ class IrHttp(models.AbstractModel): routing = submap(endpoint.routing, ROUTING_KEYS) if routing['methods'] is not None and 'OPTIONS' not in routing['methods']: routing['methods'] = routing['methods'] + ['OPTIONS'] - rule = werkzeug.routing.Rule(url, endpoint=endpoint, **routing) + rule = FasterRule(url, endpoint=endpoint, **routing) rule.merge_slashes = False routing_map.add(rule) cls._routing_map[key] = routing_map