Avoid using lcons and list_delete_first where it's easy to do so.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Jul 2019 15:15:28 +0000 (11:15 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Jul 2019 15:15:34 +0000 (11:15 -0400)
Formerly, lcons was about the same speed as lappend, but with the new
List implementation, that's not so; with a long List, data movement
imposes an O(N) cost on lcons and list_delete_first, but not lappend.

Hence, invent list_delete_last with semantics parallel to
list_delete_first (but O(1) cost), and change various places to use
lappend and list_delete_last where this can be done without much
violence to the code logic.

There are quite a few places that construct result lists using lcons not
lappend.  Some have semantic rationales for that; I added comments about
it to a couple that didn't have them already.  In many such places though,
I think the coding is that way only because back in the dark ages lcons
was faster than lappend.  Hence, switch to lappend where this can be done
without causing semantic changes.

In ExecInitExprRec(), this results in aggregates and window functions that
are in the same plan node being executed in a different order than before.
Generally, the executions of such functions ought to be independent of
each other, so this shouldn't result in visibly different query results.
But if you push it, as one regression test case does, you can show that
the order is different.  The new order seems saner; it's closer to
the order of the functions in the query text.  And we never documented
or promised anything about this, anyway.

Also, in gistfinishsplit(), don't bother building a reverse-order list;
it's easy now to iterate backwards through the original list.

It'd be possible to go further towards removing uses of lcons and
list_delete_first, but it'd require more extensive logic changes,
and I'm not convinced it's worth it.  Most of the remaining uses
deal with queues that probably never get long enough to be worth
sweating over.  (Actually, I doubt that any of the changes in this
patch will have measurable performance effects either.  But better
to have good examples than bad ones in the code base.)

Patch by me, thanks to David Rowley and Daniel Gustafsson for review.

Discussion: https://postgr.es/m/21272.1563318411@sss.pgh.pa.us

15 files changed:
src/backend/access/gist/gist.c
src/backend/catalog/heap.c
src/backend/commands/cluster.c
src/backend/commands/lockcmds.c
src/backend/commands/tablecmds.c
src/backend/commands/typecmds.c
src/backend/executor/execExpr.c
src/backend/nodes/list.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/plancat.c
src/backend/parser/parse_agg.c
src/backend/rewrite/rewriteHandler.c
src/backend/utils/adt/selfuncs.c
src/include/nodes/pg_list.h
src/test/regress/expected/aggregates.out

index dfb51f609d846a81cb59d7d88413376d438d3707..169bf6fcfed316e0a50a66e7b60015376b09198c 100644 (file)
@@ -1323,8 +1323,6 @@ static void
 gistfinishsplit(GISTInsertState *state, GISTInsertStack *stack,
                                GISTSTATE *giststate, List *splitinfo, bool unlockbuf)
 {
-       ListCell   *lc;
-       List       *reversed;
        GISTPageSplitInfo *right;
        GISTPageSplitInfo *left;
        IndexTuple      tuples[2];
@@ -1339,14 +1337,6 @@ gistfinishsplit(GISTInsertState *state, GISTInsertStack *stack,
         * left. Finally insert the downlink for the last new page and update the
         * downlink for the original page as one operation.
         */
-
-       /* for convenience, create a copy of the list in reverse order */
-       reversed = NIL;
-       foreach(lc, splitinfo)
-       {
-               reversed = lcons(lfirst(lc), reversed);
-       }
-
        LockBuffer(stack->parent->buffer, GIST_EXCLUSIVE);
        gistFindCorrectParent(state->r, stack);
 
@@ -1354,10 +1344,10 @@ gistfinishsplit(GISTInsertState *state, GISTInsertStack *stack,
         * insert downlinks for the siblings from right to left, until there are
         * only two siblings left.
         */
-       while (list_length(reversed) > 2)
+       for (int pos = list_length(splitinfo) - 1; pos > 1; pos--)
        {
-               right = (GISTPageSplitInfo *) linitial(reversed);
-               left = (GISTPageSplitInfo *) lsecond(reversed);
+               right = (GISTPageSplitInfo *) list_nth(splitinfo, pos);
+               left = (GISTPageSplitInfo *) list_nth(splitinfo, pos - 1);
 
                if (gistinserttuples(state, stack->parent, giststate,
                                                         &right->downlink, 1,
@@ -1371,11 +1361,10 @@ gistfinishsplit(GISTInsertState *state, GISTInsertStack *stack,
                        gistFindCorrectParent(state->r, stack);
                }
                /* gistinserttuples() released the lock on right->buf. */
-               reversed = list_delete_first(reversed);
        }
 
-       right = (GISTPageSplitInfo *) linitial(reversed);
-       left = (GISTPageSplitInfo *) lsecond(reversed);
+       right = (GISTPageSplitInfo *) lsecond(splitinfo);
+       left = (GISTPageSplitInfo *) linitial(splitinfo);
 
        /*
         * Finally insert downlink for the remaining right page and update the
index 6c3ff762df6021de9fd5d49ee2a8c3d49523ae6e..032fab9ac4d1fba221a9bdd19778cc847a4e1f14 100644 (file)
@@ -633,7 +633,7 @@ CheckAttributeType(const char *attname,
                                         errmsg("composite type %s cannot be made a member of itself",
                                                        format_type_be(atttypid))));
 
-               containing_rowtypes = lcons_oid(atttypid, containing_rowtypes);
+               containing_rowtypes = lappend_oid(containing_rowtypes, atttypid);
 
                relation = relation_open(get_typ_typrelid(atttypid), AccessShareLock);
 
@@ -653,7 +653,7 @@ CheckAttributeType(const char *attname,
 
                relation_close(relation, AccessShareLock);
 
-               containing_rowtypes = list_delete_first(containing_rowtypes);
+               containing_rowtypes = list_delete_last(containing_rowtypes);
        }
        else if (OidIsValid((att_typelem = get_element_type(atttypid))))
        {
index ebaec4f8dd9e9b3935d0134ec3547e390e26b620..cedb4ee844d91846b7ca2a3e4fcb238a582b3fdd 100644 (file)
@@ -1566,7 +1566,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
                rvtc = (RelToCluster *) palloc(sizeof(RelToCluster));
                rvtc->tableOid = index->indrelid;
                rvtc->indexOid = index->indexrelid;
-               rvs = lcons(rvtc, rvs);
+               rvs = lappend(rvs, rvtc);
 
                MemoryContextSwitchTo(old_context);
        }
index 417d595a7fd375d6129e9ddaafce5e6ee69cfb60..bae3b38f77846d702d60883ee3014920fc09f0b1 100644 (file)
@@ -281,11 +281,11 @@ LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, List *ancestor_views
        context.nowait = nowait;
        context.viewowner = view->rd_rel->relowner;
        context.viewoid = reloid;
-       context.ancestor_views = lcons_oid(reloid, ancestor_views);
+       context.ancestor_views = lappend_oid(ancestor_views, reloid);
 
        LockViewRecurse_walker((Node *) viewquery, &context);
 
-       ancestor_views = list_delete_oid(ancestor_views, reloid);
+       (void) list_delete_last(context.ancestor_views);
 
        table_close(view, NoLock);
 }
index 0c0ddd58c54a2126fafeece05a3141dd99d42f9f..fc1c4dfa4caebccfb1f8962a8ea45919c9a29bc7 100644 (file)
@@ -14480,6 +14480,11 @@ register_on_commit_action(Oid relid, OnCommitAction action)
        oc->creating_subid = GetCurrentSubTransactionId();
        oc->deleting_subid = InvalidSubTransactionId;
 
+       /*
+        * We use lcons() here so that ON COMMIT actions are processed in reverse
+        * order of registration.  That might not be essential but it seems
+        * reasonable.
+        */
        on_commits = lcons(oc, on_commits);
 
        MemoryContextSwitchTo(oldcxt);
index e9c8873ade9bfc4f341b285aa46cbeac67df37a6..89887b8fd75f9d09d731a827a7f495152cb78d81 100644 (file)
@@ -2999,7 +2999,7 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
                        rtc->rel = rel;
                        rtc->natts = 0;
                        rtc->atts = (int *) palloc(sizeof(int) * RelationGetNumberOfAttributes(rel));
-                       result = lcons(rtc, result);
+                       result = lappend(result, rtc);
                }
 
                /*
index e4e05753eee0152e102bfa39487f987066e26270..6d09f2a21812c4868b7928523fca7dfd26318250 100644 (file)
@@ -786,7 +786,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                {
                                        AggState   *aggstate = (AggState *) state->parent;
 
-                                       aggstate->aggs = lcons(astate, aggstate->aggs);
+                                       aggstate->aggs = lappend(aggstate->aggs, astate);
                                        aggstate->numaggs++;
                                }
                                else
@@ -834,7 +834,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                        WindowAggState *winstate = (WindowAggState *) state->parent;
                                        int                     nfuncs;
 
-                                       winstate->funcs = lcons(wfstate, winstate->funcs);
+                                       winstate->funcs = lappend(winstate->funcs, wfstate);
                                        nfuncs = ++winstate->numfuncs;
                                        if (wfunc->winagg)
                                                winstate->numaggs++;
index 5584fa87c2ddb2fcc3f9a3b17730d8222baf663b..9163464de24dff9e128288ee74c74fcc97cc6e9f 100644 (file)
@@ -826,6 +826,30 @@ list_delete_first(List *list)
        return list_delete_nth_cell(list, 0);
 }
 
+/*
+ * Delete the last element of the list.
+ *
+ * This is the opposite of list_delete_first(), but is noticeably cheaper
+ * with a long list, since no data need be moved.
+ */
+List *
+list_delete_last(List *list)
+{
+       check_list_invariants(list);
+
+       if (list == NIL)
+               return NIL;                             /* would an error be better? */
+
+       /* list_truncate won't free list if it goes to empty, but this should */
+       if (list_length(list) <= 1)
+       {
+               list_free(list);
+               return NIL;
+       }
+
+       return list_truncate(list, list_length(list) - 1);
+}
+
 /*
  * Generate the union of two lists. This is calculated by copying
  * list1 via list_copy(), then adding to it all the members of list2
index f0e789f37e6ab72cfa68f825670ae684f668489a..99dbf8da189173a022b3f013bb60ced6dd2b6d0d 100644 (file)
@@ -881,11 +881,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
                foreach(l, proot->init_plans)
                {
                        SubPlan    *initsubplan = (SubPlan *) lfirst(l);
-                       ListCell   *l2;
 
-                       foreach(l2, initsubplan->setParam)
-                               context.safe_param_ids = lcons_int(lfirst_int(l2),
-                                                                                                  context.safe_param_ids);
+                       context.safe_param_ids = list_concat(context.safe_param_ids,
+                                                                                                initsubplan->setParam);
                }
        }
 
@@ -1015,6 +1013,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
                                                                                          context->safe_param_ids);
                if (max_parallel_hazard_walker(subplan->testexpr, context))
                        return true;            /* no need to restore safe_param_ids */
+               list_free(context->safe_param_ids);
                context->safe_param_ids = save_safe_param_ids;
                /* we must also check args, but no special Param treatment there */
                if (max_parallel_hazard_walker((Node *) subplan->args, context))
@@ -4185,8 +4184,8 @@ add_function_defaults(List *args, HeapTuple func_tuple)
        ndelete = nargsprovided + list_length(defaults) - funcform->pronargs;
        if (ndelete < 0)
                elog(ERROR, "not enough default arguments");
-       while (ndelete-- > 0)
-               defaults = list_delete_first(defaults);
+       if (ndelete > 0)
+               defaults = list_copy_tail(defaults, ndelete);
 
        /* And form the combined argument list, not modifying the input list */
        return list_concat(list_copy(args), defaults);
@@ -4701,9 +4700,9 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
         * Recursively try to simplify the modified expression.  Here we must add
         * the current function to the context list of active functions.
         */
-       context->active_fns = lcons_oid(funcid, context->active_fns);
+       context->active_fns = lappend_oid(context->active_fns, funcid);
        newexpr = eval_const_expressions_mutator(newexpr, context);
-       context->active_fns = list_delete_first(context->active_fns);
+       context->active_fns = list_delete_last(context->active_fns);
 
        error_context_stack = sqlerrcontext.previous;
 
index 6ea625a148ccf314a426a5aa775dd851fdf87b57..98e99481c66225659906ed40b1d7baca91e939b1 100644 (file)
@@ -419,6 +419,13 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
                        index_close(indexRelation, NoLock);
 
+                       /*
+                        * We've historically used lcons() here.  It'd make more sense to
+                        * use lappend(), but that causes the planner to change behavior
+                        * in cases where two indexes seem equally attractive.  For now,
+                        * stick with lcons() --- few tables should have so many indexes
+                        * that the O(N^2) behavior of lcons() is really a problem.
+                        */
                        indexinfos = lcons(info, indexinfos);
                }
 
@@ -1339,7 +1346,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
                        info->kind = STATS_EXT_NDISTINCT;
                        info->keys = bms_copy(keys);
 
-                       stainfos = lcons(info, stainfos);
+                       stainfos = lappend(stainfos, info);
                }
 
                if (statext_is_kind_built(dtup, STATS_EXT_DEPENDENCIES))
@@ -1351,7 +1358,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
                        info->kind = STATS_EXT_DEPENDENCIES;
                        info->keys = bms_copy(keys);
 
-                       stainfos = lcons(info, stainfos);
+                       stainfos = lappend(stainfos, info);
                }
 
                if (statext_is_kind_built(dtup, STATS_EXT_MCV))
@@ -1363,7 +1370,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
                        info->kind = STATS_EXT_MCV;
                        info->keys = bms_copy(keys);
 
-                       stainfos = lcons(info, stainfos);
+                       stainfos = lappend(stainfos, info);
                }
 
                ReleaseSysCache(htup);
index 19e3164afbbf6c245f609ae7359365bd84aa4924..354030e549b76190fc3147fc4c475523afd2823d 100644 (file)
@@ -1132,7 +1132,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
                if (expr == NULL)
                        continue;                       /* probably cannot happen */
 
-               groupClauses = lcons(expr, groupClauses);
+               groupClauses = lappend(groupClauses, expr);
        }
 
        /*
index 5b047d166295a1741017745abe71599f4201a34b..93b678402328278c9a57aa4d9c872d429086eb56 100644 (file)
@@ -1973,7 +1973,7 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                                                        (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                                                         errmsg("infinite recursion detected in rules for relation \"%s\"",
                                                                        RelationGetRelationName(rel))));
-                               activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+                               activeRIRs = lappend_oid(activeRIRs, RelationGetRelid(rel));
 
                                foreach(l, locks)
                                {
@@ -1986,7 +1986,7 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                                                                                                  activeRIRs);
                                }
 
-                               activeRIRs = list_delete_first(activeRIRs);
+                               activeRIRs = list_delete_last(activeRIRs);
                        }
                }
 
@@ -2059,7 +2059,7 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                                                         errmsg("infinite recursion detected in policy for relation \"%s\"",
                                                                        RelationGetRelationName(rel))));
 
-                               activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+                               activeRIRs = lappend_oid(activeRIRs, RelationGetRelid(rel));
 
                                /*
                                 * get_row_security_policies just passed back securityQuals
@@ -2084,7 +2084,7 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                                expression_tree_walker((Node *) withCheckOptions,
                                                                           fireRIRonSubLink, (void *) activeRIRs);
 
-                               activeRIRs = list_delete_first(activeRIRs);
+                               activeRIRs = list_delete_last(activeRIRs);
                        }
 
                        /*
@@ -3711,7 +3711,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
                        rev = (rewrite_event *) palloc(sizeof(rewrite_event));
                        rev->relation = RelationGetRelid(rt_entry_relation);
                        rev->event = event;
-                       rewrite_events = lcons(rev, rewrite_events);
+                       rewrite_events = lappend(rewrite_events, rev);
 
                        foreach(n, product_queries)
                        {
@@ -3722,7 +3722,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
                                rewritten = list_concat(rewritten, newstuff);
                        }
 
-                       rewrite_events = list_delete_first(rewrite_events);
+                       rewrite_events = list_delete_last(rewrite_events);
                }
 
                /*
index 66449b85b18c85c93ef87dd923f0a905f67c4990..7eba59eff324d165f06493af4b4249163abaccbc 100644 (file)
@@ -3201,7 +3201,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
                 * Split the list of varinfos in two - one for the current rel, one
                 * for remaining Vars on other rels.
                 */
-               relvarinfos = lcons(varinfo1, relvarinfos);
+               relvarinfos = lappend(relvarinfos, varinfo1);
                for_each_cell(l, varinfos, list_second_cell(varinfos))
                {
                        GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l);
@@ -3209,12 +3209,12 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
                        if (varinfo2->rel == varinfo1->rel)
                        {
                                /* varinfos on current rel */
-                               relvarinfos = lcons(varinfo2, relvarinfos);
+                               relvarinfos = lappend(relvarinfos, varinfo2);
                        }
                        else
                        {
                                /* not time to process varinfo2 yet */
-                               newvarinfos = lcons(varinfo2, newvarinfos);
+                               newvarinfos = lappend(newvarinfos, varinfo2);
                        }
                }
 
index 71dc4dc33a55da0d75890d8a1220ad4ba48399d4..146340877c39f0106014453349bb412ce4811647 100644 (file)
@@ -531,6 +531,7 @@ extern List *list_delete_ptr(List *list, void *datum);
 extern List *list_delete_int(List *list, int datum);
 extern List *list_delete_oid(List *list, Oid datum);
 extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
 extern List *list_delete_nth_cell(List *list, int n);
 extern List *list_delete_cell(List *list, ListCell *cell);
 
index ef8eec3fbf2cdd5c0cd1718ee84f5add6c2e312b..be4ddf86a437c7dc6557139c4db6b4a42350794c 100644 (file)
@@ -2030,10 +2030,10 @@ NOTICE:  avg_transfn called with 3
 
 -- this should not share the state due to different input columns.
 select my_avg(one),my_sum(two) from (values(1,2),(3,4)) t(one,two);
-NOTICE:  avg_transfn called with 2
 NOTICE:  avg_transfn called with 1
-NOTICE:  avg_transfn called with 4
+NOTICE:  avg_transfn called with 2
 NOTICE:  avg_transfn called with 3
+NOTICE:  avg_transfn called with 4
  my_avg | my_sum 
 --------+--------
       2 |      6