From 90b206894a2c442a5c475a3d90fff8f89a9b3ce0 Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Fri, 31 Aug 2018 21:35:14 +0100 Subject: [PATCH] add caching to _build_regex_range - _build_regex_range is called 17 times on import of nova.api.validation.parameters_types. _build_regex_range internally calls re.escape and valid_char on every char returned from _get_all_chars. _get_all_chars yields all chars up to 0xffff. As a result re.escape and valid_char are called 1.1 million times when nova.api.validation.parameters_types is imported. - This change add a memorize decorator and uses it to cache _build_regex_range - This change does not cache valid_char, _is_printable or re.escape as hashing and caching them for each invocation would be far more costly both in time and memory than computing the result. Change-Id: Ic1f2c560a6da815b26fdf770450bbe439d18d4f9 Closes-Bug: #1790195 --- nova/api/validation/parameter_types.py | 23 +++++++++++++++++++++++ nova/tests/unit/test_api_validation.py | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/nova/api/validation/parameter_types.py b/nova/api/validation/parameter_types.py index 8c54c1993d58..57f8fb1e63f8 100644 --- a/nova/api/validation/parameter_types.py +++ b/nova/api/validation/parameter_types.py @@ -16,6 +16,7 @@ Common parameter types for validating request Body. """ import copy +import functools import re import unicodedata @@ -24,6 +25,27 @@ import six from nova.i18n import _ from nova.objects import tag +_REGEX_RANGE_CACHE = {} + + +def memorize(func): + + @functools.wraps(func) + def memorizer(*args, **kwargs): + global _REGEX_RANGE_CACHE + key = "%s:%s:%s" % (func.__name__, hash(str(args)), hash(str(kwargs))) + value = _REGEX_RANGE_CACHE.get(key) + if value is None: + value = func(*args, **kwargs) + _REGEX_RANGE_CACHE[key] = value + return value + return memorizer + + +def _reset_cache(): + global _REGEX_RANGE_CACHE + _REGEX_RANGE_CACHE = {} + def single_param(schema): """Macro function for use in JSONSchema to support query parameters that @@ -83,6 +105,7 @@ def _get_all_chars(): # constraint fails and this causes issues for some unittests when # PYTHONHASHSEED is set randomly. +@memorize def _build_regex_range(ws=True, invert=False, exclude=None): """Build a range regex for a set of characters in utf8. diff --git a/nova/tests/unit/test_api_validation.py b/nova/tests/unit/test_api_validation.py index 7c6bad7424da..db202276c899 100644 --- a/nova/tests/unit/test_api_validation.py +++ b/nova/tests/unit/test_api_validation.py @@ -95,7 +95,9 @@ class ValidationRegex(test.NoDBTestCase): self.useFixture(fixtures.MonkeyPatch( 'nova.api.validation.parameter_types._get_all_chars', _get_all_chars)) - + # note that since we use only the ascii range in the tests + # we have to clear the cache to recompute them. + parameter_types._reset_cache() r = parameter_types._build_regex_range(ws=False) self.assertEqual(r, re.escape('!') + '-' + re.escape('~'))