Commit ff723d89 authored by Anssi Kääriäinen's avatar Anssi Kääriäinen Committed by Anssi Kääriäinen
Browse files

Fixed #20950 -- Instantiate OrderedDict() only when needed

The use of OrderedDict (even an empty one) was surprisingly slow. By
initializing OrderedDict only when needed it is possible to save
non-trivial amount of computing time (Model.save() is around 30% faster
for example).

This commit targetted sql.Query only, there are likely other places
which could use similar optimizations.
parent 886bb9d8
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -391,7 +391,7 @@ class SQLCompiler(object):
                    if not distinct or elt in select_aliases:
                        result.append('%s %s' % (elt, order))
                        group_by.append((elt, []))
            elif get_order_dir(field)[0] not in self.query.extra:
            elif not self.query._extra or get_order_dir(field)[0] not in self.query._extra:
                # 'col' is of the form 'field' or 'field1__field2' or
                # '-field1__field2__field', etc.
                for table, cols, order in self.find_ordering_name(field,
@@ -987,7 +987,7 @@ class SQLUpdateCompiler(SQLCompiler):
        # We need to use a sub-select in the where clause to filter on things
        # from other tables.
        query = self.query.clone(klass=Query)
        query.extra = {}
        query._extra = {}
        query.select = []
        query.add_fields([query.get_meta().pk.name])
        # Recheck the count - it is possible that fiddling with the select
+45 −20
Original line number Diff line number Diff line
@@ -143,7 +143,10 @@ class Query(object):
        self.select_related = False

        # SQL aggregate-related attributes
        self.aggregates = OrderedDict() # Maps alias -> SQL aggregate function
        # The _aggregates will be an OrderedDict when used. Due to the cost
        # of creating OrderedDict this attribute is created lazily (in
        # self.aggregates property).
        self._aggregates = None # Maps alias -> SQL aggregate function
        self.aggregate_select_mask = None
        self._aggregate_select_cache = None

@@ -153,7 +156,9 @@ class Query(object):

        # These are for extensions. The contents are more or less appended
        # verbatim to the appropriate clause.
        self.extra = OrderedDict()  # Maps col_alias -> (col_sql, params).
        # The _extra attribute is an OrderedDict, lazily created similarly to
        # .aggregates
        self._extra = None  # Maps col_alias -> (col_sql, params).
        self.extra_select_mask = None
        self._extra_select_cache = None

@@ -165,6 +170,18 @@ class Query(object):
        # load.
        self.deferred_loading = (set(), True)

    @property
    def extra(self):
        if self._extra is None:
            self._extra = OrderedDict()
        return self._extra

    @property
    def aggregates(self):
        if self._aggregates is None:
            self._aggregates = OrderedDict()
        return self._aggregates

    def __str__(self):
        """
        Returns the query as a string of SQL with the parameter values
@@ -245,7 +262,7 @@ class Query(object):
        obj.select_for_update_nowait = self.select_for_update_nowait
        obj.select_related = self.select_related
        obj.related_select_cols = []
        obj.aggregates = self.aggregates.copy()
        obj._aggregates = self._aggregates.copy() if self._aggregates is not None else None
        if self.aggregate_select_mask is None:
            obj.aggregate_select_mask = None
        else:
@@ -257,7 +274,7 @@ class Query(object):
        # used.
        obj._aggregate_select_cache = None
        obj.max_depth = self.max_depth
        obj.extra = self.extra.copy()
        obj._extra = self._extra.copy() if self._extra is not None else None
        if self.extra_select_mask is None:
            obj.extra_select_mask = None
        else:
@@ -344,7 +361,7 @@ class Query(object):
            # and move them to the outer AggregateQuery.
            for alias, aggregate in self.aggregate_select.items():
                if aggregate.is_summary:
                    query.aggregate_select[alias] = aggregate.relabeled_clone(relabels)
                    query.aggregates[alias] = aggregate.relabeled_clone(relabels)
                    del obj.aggregate_select[alias]

            try:
@@ -358,7 +375,7 @@ class Query(object):
            query = self
            self.select = []
            self.default_cols = False
            self.extra = {}
            self._extra = {}
            self.remove_inherited_models()

        query.clear_ordering(True)
@@ -527,7 +544,7 @@ class Query(object):
            # It would be nice to be able to handle this, but the queries don't
            # really make sense (or return consistent value sets). Not worth
            # the extra complexity when you can write a real query instead.
            if self.extra and rhs.extra:
            if self._extra and rhs._extra:
                raise ValueError("When merging querysets using 'or', you "
                        "cannot have extra(select=...) on both sides.")
        self.extra.update(rhs.extra)
@@ -756,8 +773,9 @@ class Query(object):
            self.group_by = [relabel_column(col) for col in self.group_by]
        self.select = [SelectInfo(relabel_column(s.col), s.field)
                       for s in self.select]
        self.aggregates = OrderedDict(
            (key, relabel_column(col)) for key, col in self.aggregates.items())
        if self._aggregates:
            self._aggregates = OrderedDict(
                (key, relabel_column(col)) for key, col in self._aggregates.items())

        # 2. Rename the alias in the internal table/alias datastructures.
        for ident, aliases in self.join_map.items():
@@ -967,7 +985,7 @@ class Query(object):
        """
        opts = model._meta
        field_list = aggregate.lookup.split(LOOKUP_SEP)
        if len(field_list) == 1 and aggregate.lookup in self.aggregates:
        if len(field_list) == 1 and self._aggregates and aggregate.lookup in self.aggregates:
            # Aggregate is over an annotation
            field_name = field_list[0]
            col = field_name
@@ -1049,7 +1067,7 @@ class Query(object):
        lookup_parts = lookup.split(LOOKUP_SEP)
        num_parts = len(lookup_parts)
        if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms
                and lookup not in self.aggregates):
                and (not self._aggregates or lookup not in self._aggregates)):
            # Traverse the lookup query to distinguish related fields from
            # lookup types.
            lookup_model = self.model
@@ -1108,6 +1126,7 @@ class Query(object):
        value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)

        clause = self.where_class()
        if self._aggregates:
            for alias, aggregate in self.aggregates.items():
                if alias in (parts[0], LOOKUP_SEP.join(parts)):
                    clause.add((aggregate, lookup_type, value), AND)
@@ -1170,6 +1189,8 @@ class Query(object):
        Returns whether or not all elements of this q_object need to be put
        together in the HAVING clause.
        """
        if not self._aggregates:
            return False
        if not isinstance(obj, Node):
            return (refs_aggregate(obj[0].split(LOOKUP_SEP), self.aggregates)
                    or (hasattr(obj[1], 'contains_aggregate')
@@ -1632,7 +1653,7 @@ class Query(object):

        # Set only aggregate to be the count column.
        # Clear out the select cache to reflect the new unmasked aggregates.
        self.aggregates = {None: count}
        self._aggregates = {None: count}
        self.set_aggregate_mask(None)
        self.group_by = None

@@ -1781,7 +1802,8 @@ class Query(object):
            self.extra_select_mask = set(names)
        self._extra_select_cache = None

    def _aggregate_select(self):
    @property
    def aggregate_select(self):
        """The OrderedDict of aggregate columns that are not masked, and should
        be used in the SELECT clause.

@@ -1789,6 +1811,8 @@ class Query(object):
        """
        if self._aggregate_select_cache is not None:
            return self._aggregate_select_cache
        elif not self._aggregates:
            return {}
        elif self.aggregate_select_mask is not None:
            self._aggregate_select_cache = OrderedDict(
                (k, v) for k, v in self.aggregates.items()
@@ -1797,11 +1821,13 @@ class Query(object):
            return self._aggregate_select_cache
        else:
            return self.aggregates
    aggregate_select = property(_aggregate_select)

    def _extra_select(self):
    @property
    def extra_select(self):
        if self._extra_select_cache is not None:
            return self._extra_select_cache
        if not self._extra:
            return {}
        elif self.extra_select_mask is not None:
            self._extra_select_cache = OrderedDict(
                (k, v) for k, v in self.extra.items()
@@ -1810,7 +1836,6 @@ class Query(object):
            return self._extra_select_cache
        else:
            return self.extra
    extra_select = property(_extra_select)

    def trim_start(self, names_with_path):
        """