diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-07-13 16:28:42 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-07-13 16:29:07 -0400 |
| commit | 731a4daf63dc0fdb784d195e89c5f357420657fb (patch) | |
| tree | 274bd30f51930e070c086b9a8cc34b4e41b52547 /lib/sqlalchemy | |
| parent | fd8b2d188c58626bdc0d2f11341bc99ba81ae91d (diff) | |
| download | sqlalchemy-731a4daf63dc0fdb784d195e89c5f357420657fb.tar.gz | |
A performance fix related to the usage of the :func:`.defer` option
when loading mapped entities. The function overhead of applying
a per-object deferred callable to an instance at load time was
significantly higher than that of just loading the data from the row
(note that ``defer()`` is meant to reduce DB/network overhead, not
necessarily function call count); the function call overhead is now
less than that of loading data from the column in all cases. There
is also a reduction in the number of "lazy callable" objects created
per load from N (total deferred values in the result) to 1 (total
number of deferred cols).
[ticket:2778]
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/loading.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/state.py | 23 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 43 |
4 files changed, 38 insertions, 32 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index a4e5977a9..13c2cf256 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -605,7 +605,7 @@ class AttributeImpl(object): if key in state.callables: callable_ = state.callables[key] - value = callable_(passive) + value = callable_(state, passive) elif self.callable_: value = self.callable_(state, passive) else: diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index e1f4d1b7c..1641f509e 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -161,7 +161,7 @@ def get_from_identity(session, key, passive): # expired state will be checked soon enough, if necessary return instance try: - state(passive) + state(state, passive) except orm_exc.ObjectDeletedError: session._remove_newly_deleted([state]) return None diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 6ade91b3e..c479d880d 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -305,14 +305,19 @@ class InstanceState(interfaces._InspectionAttr): dict_.pop(key, None) self.callables[key] = self - def _set_callable(self, dict_, key, callable_): - """Remove the given attribute and set the given callable - as a loader.""" - - old = dict_.pop(key, None) - if old is not None and self.manager[key].impl.collection: - self.manager[key].impl._invalidate_collection(old) - self.callables[key] = callable_ + @classmethod + def _row_processor(cls, manager, fn, key): + impl = manager[key].impl + if impl.collection: + def _set_callable(state, dict_, row): + old = dict_.pop(key, None) + if old is not None: + impl._invalidate_collection(old) + state.callables[key] = fn + else: + def _set_callable(state, dict_, row): + state.callables[key] = fn + return _set_callable def _expire(self, dict_, modified_set): self.expired = True @@ -359,7 +364,7 @@ class InstanceState(interfaces._InspectionAttr): self.manager.dispatch.expire(self, attribute_names) - def __call__(self, passive): + def __call__(self, state, passive): """__call__ allows the InstanceState to act as a deferred callable for loading expired attributes, which is also serializable (picklable). diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 6394003b3..aa46d06a8 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -14,6 +14,7 @@ from . import ( attributes, interfaces, exc as orm_exc, loading, unitofwork, util as orm_util ) +from .state import InstanceState from .util import _none_set from .interfaces import ( LoaderStrategy, StrategizedOption, MapperOption, PropertyOption, @@ -181,9 +182,9 @@ class DeferredColumnLoader(LoaderStrategy): context, path, mapper, row, adapter) elif not self.is_class_level: - def set_deferred_for_local_state(state, dict_, row): - state._set_callable( - dict_, key, LoadDeferredColumns(state, key)) + set_deferred_for_local_state = InstanceState._row_processor( + mapper.class_manager, + LoadDeferredColumns(key), key) return set_deferred_for_local_state, None, None else: def reset_col_for_deferred(state, dict_, row): @@ -256,12 +257,11 @@ log.class_logger(DeferredColumnLoader) class LoadDeferredColumns(object): """serializable loader object used by DeferredColumnLoader""" - def __init__(self, state, key): - self.state = state + def __init__(self, key): self.key = key - def __call__(self, passive=attributes.PASSIVE_OFF): - state, key = self.state, self.key + def __call__(self, state, passive=attributes.PASSIVE_OFF): + key = self.key localparent = state.manager.mapper prop = localparent._props[key] @@ -602,16 +602,18 @@ class LazyLoader(AbstractRelationshipLoader): mapper, row, adapter): key = self.key if not self.is_class_level: - def set_lazy_callable(state, dict_, row): - # we are not the primary manager for this attribute - # on this class - set up a - # per-instance lazyloader, which will override the - # class-level behavior. - # this currently only happens when using a - # "lazyload" option on a "no load" - # attribute - "eager" attributes always have a - # class-level lazyloader installed. - state._set_callable(dict_, key, LoadLazyAttribute(state, key)) + # we are not the primary manager for this attribute + # on this class - set up a + # per-instance lazyloader, which will override the + # class-level behavior. + # this currently only happens when using a + # "lazyload" option on a "no load" + # attribute - "eager" attributes always have a + # class-level lazyloader installed. + set_lazy_callable = InstanceState._row_processor( + mapper.class_manager, + LoadLazyAttribute(key), key) + return set_lazy_callable, None, None else: def reset_for_lazy_callable(state, dict_, row): @@ -634,12 +636,11 @@ log.class_logger(LazyLoader) class LoadLazyAttribute(object): """serializable loader object used by LazyLoader""" - def __init__(self, state, key): - self.state = state + def __init__(self, key): self.key = key - def __call__(self, passive=attributes.PASSIVE_OFF): - state, key = self.state, self.key + def __call__(self, state, passive=attributes.PASSIVE_OFF): + key = self.key instance_mapper = state.manager.mapper prop = instance_mapper._props[key] strategy = prop._strategies[LazyLoader] |
