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
This commit is contained in:
Sean Mooney 2018-08-31 21:35:14 +01:00 committed by Alex Xu
parent 6522ea3ecf
commit 90b206894a
2 changed files with 26 additions and 1 deletions

View File

@ -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.

View File

@ -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('~'))