Refs #29908 -- Optimized known related objects assignment.

Since CPython implements a C level attrgetter(*attrs) it even outperforms the
most common case of a single known related object since the resulting attribute
values tuple is built in C.
This commit is contained in:
Simon Charette 2018-11-16 17:23:12 -05:00
parent f436c82637
commit 0cf85e6b07
2 changed files with 19 additions and 13 deletions

View File

@ -63,6 +63,7 @@ and two directions (forward and reverse) for a total of six combinations.
``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead. ``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
""" """
from django.core.exceptions import FieldError
from django.db import connections, router, transaction from django.db import connections, router, transaction
from django.db.models import Q, signals from django.db.models import Q, signals
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
@ -581,10 +582,17 @@ def create_reverse_many_to_one_manager(superclass, rel):
# that abuse create_reverse_many_to_one_manager() with reverse # that abuse create_reverse_many_to_one_manager() with reverse
# one-to-many relationships instead and break known related # one-to-many relationships instead and break known related
# objects assignment. # objects assignment.
rel_obj_id = tuple([ try:
getattr(self.instance, target_field.attname) target_field = self.field.target_field
for target_field in self.field.get_path_info()[-1].target_fields except FieldError:
]) # The relationship has multiple target fields. Use a tuple
# for related object id.
rel_obj_id = tuple([
getattr(self.instance, target_field.attname)
for target_field in self.field.get_path_info()[-1].target_fields
])
else:
rel_obj_id = getattr(self.instance, target_field.attname)
queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}} queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}}
return queryset return queryset

View File

@ -62,14 +62,12 @@ class ModelIterable(BaseIterable):
for f in select[model_fields_start:model_fields_end]] for f in select[model_fields_start:model_fields_end]]
related_populators = get_related_populators(klass_info, select, db) related_populators = get_related_populators(klass_info, select, db)
known_related_objects = [ known_related_objects = [
(field, related_objs, [ (field, related_objs, operator.attrgetter(*[
operator.attrgetter( field.attname
field.attname if from_field == 'self' else
if from_field == 'self' else queryset.model._meta.get_field(from_field).attname
queryset.model._meta.get_field(from_field).attname
)
for from_field in field.from_fields for from_field in field.from_fields
]) for field, related_objs in queryset._known_related_objects.items() ])) for field, related_objs in queryset._known_related_objects.items()
] ]
for row in compiler.results_iter(results): for row in compiler.results_iter(results):
obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end]) obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end])
@ -80,11 +78,11 @@ class ModelIterable(BaseIterable):
setattr(obj, attr_name, row[col_pos]) setattr(obj, attr_name, row[col_pos])
# Add the known related objects to the model. # Add the known related objects to the model.
for field, rel_objs, rel_getters in known_related_objects: for field, rel_objs, rel_getter in known_related_objects:
# Avoid overwriting objects loaded by, e.g., select_related(). # Avoid overwriting objects loaded by, e.g., select_related().
if field.is_cached(obj): if field.is_cached(obj):
continue continue
rel_obj_id = tuple([rel_getter(obj) for rel_getter in rel_getters]) rel_obj_id = rel_getter(obj)
try: try:
rel_obj = rel_objs[rel_obj_id] rel_obj = rel_objs[rel_obj_id]
except KeyError: except KeyError: