--- /dev/null
+.. change::
+ :tags: bug, orm, performance
+ :tickets: 7823
+
+ Improvements in memory usage by the ORM, removing a significant set of
+ intermediary expression objects that are typically stored when a copy of an
+ expression object is created. These clones have been greatly reduced,
+ reducing the number of total expression objects stored in memory by
+ ORM mappings by about 30%.
# process leaves around a lot of remnants of the previous clause
# typically in the form of column expressions still attached to the
# old table.
- c._is_clone_of = self
+ cc = self._is_clone_of
+ c._is_clone_of = cc if cc is not None else self
return c
def _clone(self, **kw):
c = self.__class__.__new__(self.__class__)
c.__dict__ = {k: v for k, v in self.__dict__.items()}
- c._is_clone_of = self
+
+ c._is_clone_of = self.__dict__.get("_is_clone_of", self)
return c
@classmethod
go()
+ def test_clone_expression(self):
+
+ root_expr = column("x", Integer) == 12
+ expr = root_expr
+
+ @profile_memory()
+ def go():
+ nonlocal expr
+
+ expr = cloned_traverse(expr, {}, {})
+
+ go()
+
@testing.add_to_marker.memory_intensive
class MemUsageWBackendTest(fixtures.MappedTest, EnsureZeroed):
"""test the current state of the hasparent() flag."""
-
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import testing
run_inserts = None
+ # trying to push GC to do a better job
+ run_setup_classes = "each"
+ run_setup_mappers = "each"
+
@classmethod
def define_tables(cls, metadata):
if testing.against("oracle"):
"""
User = self.classes.User
s, u1, a1 = self._fixture()
+ gc_collect()
u2 = User(addresses=[a1]) # noqa
s.expire(a1)
u1.addresses.remove(a1)
+ u2_is = u2._sa_instance_state
+ del u2
+
+ for i in range(5):
+ gc_collect()
+ # heisenberg the GC a little bit, since #7823 caused a lot more
+ # GC when mappings are set up, larger test suite started failing
+ # on this being gc'ed
+ o = u2_is.obj()
+ assert o is None
+
# controversy here. The action is
# to expire one object, not the other, and remove;
# this is pretty abusive in any case. for now
def test_stale_state_negative(self):
User = self.classes.User
s, u1, a1 = self._fixture()
+ gc_collect()
u2 = User(addresses=[a1])
s.add(u2)
s.flush()
s._expunge_states([attributes.instance_state(u2)])
+
+ u2_is = u2._sa_instance_state
del u2
- gc_collect()
+
+ for i in range(5):
+ gc_collect()
+ # heisenberg the GC a little bit, since #7823 caused a lot more
+ # GC when mappings are set up, larger test suite started failing
+ # on this being gc'ed
+ o = u2_is.obj()
+ assert o is None
assert_raises_message(
orm_exc.StaleDataError,
s = fixture_session()
self.mapper_registry.map_imperatively(User, users)
+ gc_collect()
s.add(User(name="ed"))
s.flush()
assert not s.dirty
user = s.query(User).one()
+
+ # heisenberg the GC a little bit, since #7823 caused a lot more
+ # GC when mappings are set up, larger test suite started failing
+ # on this being gc'ed
+ user_is = user._sa_instance_state
del user
gc_collect()
+ assert user_is.obj() is None
+
assert len(s.identity_map) == 0
user = s.query(User).one()
s = fixture_session()
self.mapper_registry.map_imperatively(User, users)
+ gc_collect()
s.add(User(name="ed"))
s.flush()
properties={"addresses": relationship(Address, backref="user")},
)
self.mapper_registry.map_imperatively(Address, addresses)
+ gc_collect()
+
s.add(User(name="ed", addresses=[Address(email_address="ed1")]))
s.commit()
},
)
self.mapper_registry.map_imperatively(Address, addresses)
+ gc_collect()
+
s.add(User(name="ed", address=Address(email_address="ed1")))
s.commit()
users, User = self.tables.users, self.classes.User
self.mapper_registry.map_imperatively(User, users)
+ gc_collect()
sess = Session(testing.db)
users, User = self.tables.users, self.classes.User
self.mapper_registry.map_imperatively(User, users)
+ gc_collect()
sess = fixture_session()