Files
sqlalchemy/test/aaa_profiling/test_misc.py
T
Mike Bayer a9b62055bf Try to measure new style caching in the ORM, take two
Supercedes: If78fbb557c6f2cae637799c3fec2cbc5ac248aaf

Trying to see if by making the cache key memoized, we
still can have the older "identity" form of caching
which is the cheapest of all, at the same time as the
newer "cache key each time" version that is not nearly
as cheap; but still much cheaper than no caching at all.

Also needed is a per-execution update of _keymap when
we invoke from a cached select, so that Column objects
that are anonymous or otherwise adapted will match up.
this is analogous to the adaption of bound parameters
from the cache key.

Adds test coverage for the keymap / construct_params()
 changes related to caching.  Also hones performance
to a large extent for statement construction and
cache key generation.

Also includes a new memoized attribute
approach that vastly simplifies the previous approach
of "group_expirable_memoized_property" and finally
integrates cleanly with _clone(), _generate(), etc.
no more hardcoding of attributes is needed, as well
as that most _reset_memoization() calls are no longer
needed as the reset is inherent in a _generate() call;
this also has dramatic performance improvements.

Change-Id: I95c560ffcbfa30b26644999412fb6a385125f663
2020-04-01 16:12:23 -04:00

127 lines
3.9 KiB
Python

from sqlalchemy import Column
from sqlalchemy import Enum
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import testing
from sqlalchemy.orm import join as ormjoin
from sqlalchemy.orm import mapper
from sqlalchemy.orm import relationship
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import profiling
from sqlalchemy.util import classproperty
class EnumTest(fixtures.TestBase):
__requires__ = ("cpython", "python_profiling_backend")
def setup(self):
class SomeEnum(object):
# Implements PEP 435 in the minimal fashion needed by SQLAlchemy
_members = {}
@classproperty
def __members__(cls):
"""simulate a very expensive ``__members__`` getter"""
for i in range(10):
x = {}
x.update({k: v for k, v in cls._members.items()}.copy())
return x.copy()
def __init__(self, name, value):
self.name = name
self.value = value
self._members[name] = self
setattr(self.__class__, name, self)
for i in range(400):
SomeEnum("some%d" % i, i)
self.SomeEnum = SomeEnum
@profiling.function_call_count()
def test_create_enum_from_pep_435_w_expensive_members(self):
Enum(self.SomeEnum)
class CacheKeyTest(fixtures.TestBase):
__requires__ = ("cpython", "python_profiling_backend")
@testing.fixture(scope="class")
def mapping_fixture(self):
# note in order to work nicely with "fixture" we are emerging
# a whole new model of setup/teardown, since pytest "fixture"
# sort of purposely works badly with setup/teardown
metadata = MetaData()
parent = Table(
"parent",
metadata,
Column("id", Integer, primary_key=True),
Column("data", String(20)),
)
child = Table(
"child",
metadata,
Column("id", Integer, primary_key=True),
Column("data", String(20)),
Column(
"parent_id", Integer, ForeignKey("parent.id"), nullable=False
),
)
class Parent(testing.entities.BasicEntity):
pass
class Child(testing.entities.BasicEntity):
pass
mapper(
Parent,
parent,
properties={"children": relationship(Child, backref="parent")},
)
mapper(Child, child)
return Parent, Child
@testing.fixture(scope="function")
def stmt_fixture_one(self, mapping_fixture):
# note that by using ORM elements we will have annotations in these
# items also which is part of the performance hit
Parent, Child = mapping_fixture
return [
(
select([Parent.id, Child.id])
.select_from(ormjoin(Parent, Child, Parent.children))
.where(Child.id == 5)
)
for i in range(100)
]
@profiling.function_call_count(variance=0.15, warmup=2)
def test_statement_key_is_cached(self, stmt_fixture_one):
current_key = None
for stmt in stmt_fixture_one:
key = stmt._generate_cache_key()
if current_key:
eq_(key, current_key)
else:
current_key = key
@profiling.function_call_count(variance=0.15, warmup=0)
def test_statement_key_is_not_cached(self, stmt_fixture_one):
current_key = None
for stmt in stmt_fixture_one:
key = stmt._generate_cache_key()
if current_key:
eq_(key, current_key)
else:
current_key = key