diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-12-19 15:59:55 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-12-21 12:20:04 -0500 |
| commit | e6cd36fc51d25922f20aa203229a636f5d6daabe (patch) | |
| tree | 661a3bbeb6ee5cbbe1f8d40aae6951d8cef12b80 /lib/sqlalchemy/sql | |
| parent | b301dd2d20f2da66f4eb69527ba2642da34c630d (diff) | |
| download | sqlalchemy-e6cd36fc51d25922f20aa203229a636f5d6daabe.tar.gz | |
implement cython for cache_anon_map, prefix_anon_map
These are small bits where cache_anon_map in particular
is part of the cache key generation scheme which is a key
target for cython.
changing such a tiny element of the cache key gen is
doing basically nothing yet, as the cython impl is
mostly the exact same speed as the python one. I guess for
cython to be effective we'd need to redo the whole cache key
generation and possibly not use the same kinds of structures,
which might not be very easy to do.
Additionally, some cython runtime import errors are being
observed on jenkins, add an upfront check to the test suite
to indicate if the expected build succeeded when REQUIRE_SQLALCHEMY_CEXT
is set.
Running case CacheAnonMap
Running python .... Done
Running cython .... Done
| python | cython | cy / py |
test_get_anon_non_present| 0.301266758 | 0.231203834 | 0.767438915 |
test_get_anon_present| 0.300919362 | 0.227336695 | 0.755473803 |
test_has_key_non_present| 0.152725077 | 0.133191719 | 0.872101171 |
test_has_key_present| 0.152689778 | 0.133673095 | 0.875455428 |
Running case PrefixAnonMap
Running python .. Done
Running cython .. Done
| python | cython | cy / py |
test_apply_non_present| 0.358715744 | 0.335245703 | 0.934572034 |
test_apply_present | 0.354434996 | 0.338579782 | 0.955266229 |
Change-Id: I0d3f1dd285c044afc234479141d831b2ee0455be
Diffstat (limited to 'lib/sqlalchemy/sql')
| -rw-r--r-- | lib/sqlalchemy/sql/_py_util.py | 59 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/base.py | 27 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/elements.py | 11 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/traversals.py | 38 |
4 files changed, 77 insertions, 58 deletions
diff --git a/lib/sqlalchemy/sql/_py_util.py b/lib/sqlalchemy/sql/_py_util.py new file mode 100644 index 000000000..ceb637609 --- /dev/null +++ b/lib/sqlalchemy/sql/_py_util.py @@ -0,0 +1,59 @@ +# sql/_py_util.py +# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors +# <see AUTHORS file> +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + + +class prefix_anon_map(dict): + """A map that creates new keys for missing key access. + + Considers keys of the form "<ident> <name>" to produce + new symbols "<name>_<index>", where "index" is an incrementing integer + corresponding to <name>. + + Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which + is otherwise usually used for this type of operation. + + """ + + def __missing__(self, key): + (ident, derived) = key.split(" ", 1) + anonymous_counter = self.get(derived, 1) + self[derived] = anonymous_counter + 1 + value = f"{derived}_{anonymous_counter}" + self[key] = value + return value + + +class cache_anon_map(dict): + """A map that creates new keys for missing key access. + + Produces an incrementing sequence given a series of unique keys. + + This is similar to the compiler prefix_anon_map class although simpler. + + Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which + is otherwise usually used for this type of operation. + + """ + + _index = 0 + + def get_anon(self, object_): + + idself = id(object_) + if idself in self: + return self[idself], True + else: + # inline of __missing__ + self[idself] = id_ = str(self._index) + self._index += 1 + + return id_, False + + def __missing__(self, key): + self[key] = val = str(self._index) + self._index += 1 + return val diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index 4165751ca..b5a20830d 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -30,6 +30,12 @@ from .. import util from ..util import HasMemoized from ..util import hybridmethod +try: + from sqlalchemy.cyextension.util import prefix_anon_map # noqa +except ImportError: + from ._py_util import prefix_anon_map # noqa + + coercions = None elements = None type_api = None @@ -1012,27 +1018,6 @@ class Executable(roles.StatementRole, Generative): return self._execution_options -class prefix_anon_map(dict): - """A map that creates new keys for missing key access. - - Considers keys of the form "<ident> <name>" to produce - new symbols "<name>_<index>", where "index" is an incrementing integer - corresponding to <name>. - - Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which - is otherwise usually used for this type of operation. - - """ - - def __missing__(self, key): - (ident, derived) = key.split(" ", 1) - anonymous_counter = self.get(derived, 1) - self[derived] = anonymous_counter + 1 - value = derived + "_" + str(anonymous_counter) - self[key] = value - return value - - class SchemaEventTarget: """Base class for elements that are the targets of :class:`.DDLEvents` events. diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 00270c9b5..08c993820 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -1659,14 +1659,9 @@ class BindParameter(roles.InElementRole, ColumnElement): anon_map[NO_CACHE] = True return None - idself = id(self) - if idself in anon_map: - return (anon_map[idself], self.__class__) - else: - # inline of - # id_ = anon_map[idself] - anon_map[idself] = id_ = str(anon_map.index) - anon_map.index += 1 + id_, found = anon_map.get_anon(self) + if found: + return (id_, self.__class__) if bindparams is not None: bindparams.append(self) diff --git a/lib/sqlalchemy/sql/traversals.py b/lib/sqlalchemy/sql/traversals.py index d58b5c2bb..22398e7c1 100644 --- a/lib/sqlalchemy/sql/traversals.py +++ b/lib/sqlalchemy/sql/traversals.py @@ -12,6 +12,12 @@ from .. import util from ..inspection import inspect from ..util import HasMemoized +try: + from sqlalchemy.cyextension.util import cache_anon_map as anon_map # noqa +except ImportError: + from ._py_util import cache_anon_map as anon_map # noqa + + SKIP_TRAVERSE = util.symbol("skip_traverse") COMPARE_FAILED = False COMPARE_SUCCEEDED = True @@ -177,16 +183,11 @@ class HasCacheKey: """ - idself = id(self) cls = self.__class__ - if idself in anon_map: - return (anon_map[idself], cls) - else: - # inline of - # id_ = anon_map[idself] - anon_map[idself] = id_ = str(anon_map.index) - anon_map.index += 1 + id_, found = anon_map.get_anon(self) + if found: + return (id_, cls) try: dispatcher = cls.__dict__["_generated_cache_key_traversal"] @@ -1030,27 +1031,6 @@ def _resolve_name_for_compare(element, name, anon_map, **kw): return name -class anon_map(dict): - """A map that creates new keys for missing key access. - - Produces an incrementing sequence given a series of unique keys. - - This is similar to the compiler prefix_anon_map class although simpler. - - Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which - is otherwise usually used for this type of operation. - - """ - - def __init__(self): - self.index = 0 - - def __missing__(self, key): - self[key] = val = str(self.index) - self.index += 1 - return val - - class TraversalComparatorStrategy(InternalTraversal, util.MemoizedSlots): __slots__ = "stack", "cache", "anon_map" |
