Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kY37h-0001Qt-GC for pgsql-hackers@arkaria.postgresql.org; Thu, 29 Oct 2020 08:17:30 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1kY37g-0004aq-EC for pgsql-hackers@arkaria.postgresql.org; Thu, 29 Oct 2020 08:17:28 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kY37g-0004ac-1L for pgsql-hackers@lists.postgresql.org; Thu, 29 Oct 2020 08:17:28 +0000 Received: from lahtoruutu.iki.fi ([185.185.170.37]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kY37d-0002cE-9W for pgsql-hackers@postgresql.org; Thu, 29 Oct 2020 08:17:27 +0000 Received: from [192.168.1.113] (dsl-hkibng22-54faa4-119.dhcp.inet.fi [84.250.164.119]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: hlinnaka) by lahtoruutu.iki.fi (Postfix) with ESMTPSA id 3223B1B006F0; Thu, 29 Oct 2020 10:17:21 +0200 (EET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iki.fi; s=lahtoruutu; t=1603959441; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=I8XVgJ5qUZEqc3wEvB7fQKvTZpHebqs/mpPVa78EeRg=; b=ZFwYqDaPPjpg2mu+4y946ZQJqSZHAPN2VkascJwoGLQnG/5CNbTTdkm9vMfUsyagP4QV7K eZ5Fkxhru4PuYjCjGWeK4J2khkEdhD400WkMatWN0rExLDXd12aRtf953hcWp8Ujyl62dq yeco3Jo2aL3hcPTfwUVRd4QQs2Ueq4jiNBP5ugzO0pITENnhcVJhSHLp3wy5sgH3FQIkwc AwwLfwcTYc0nr7qXJ0NbxSCFGZzcCz8Ou/RS/l8N+LZRbOCmF8D3RIxkq9oozQ2ijiyhFj KDF4XFv3uOJZch2ZrNAQEdUTHFyGXoqF9WWzSAxacoCWbNBgQW8xurE34lvPyg== Subject: Re: Deduplicate aggregates and transition functions in planner To: Andres Freund Cc: pgsql-hackers References: <20201028195901.t55yiwmxe3quguot@alap3.anarazel.de> From: Heikki Linnakangas Message-ID: <16a1e564-1fa5-b4ea-c512-9ca133ea4bc0@iki.fi> Date: Thu, 29 Oct 2020 10:17:20 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.4.0 MIME-Version: 1.0 In-Reply-To: <20201028195901.t55yiwmxe3quguot@alap3.anarazel.de> Content-Type: multipart/mixed; boundary="------------778645A61BB63B2AE9F12F88" Content-Language: en-US ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=iki.fi; s=lahtoruutu; t=1603959441; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=I8XVgJ5qUZEqc3wEvB7fQKvTZpHebqs/mpPVa78EeRg=; b=enXsnannF1/ojW/i0EO1ISOFDP0aimJ16jjroMuAr8dbmTWcZ8ULed3vbK2vzU4/ZIxKIb a6ix6NDC46pzCI3zyoW+HS67ffpXkvQbXN88zK04NxJ/VxjLvMm2baNuVfEwHkiEolgLRx PyHAsa/wdM4Uks48/GADHwNQPpsjOY5IspbdYW1/jS+wCR6kUfUiujHtlgBK/7vskPLvEd qbFGuVqqXpU0wOkfQfbQ3dI+G3YwMyT3FCi/XpvnKyeCl16peE2K36omgLFfaAlOXJPV6+ dP58DUcQbR6gP0QzS1ajTB/0GfXtJX2infTQL7iEbloZ4XwnRpUSfIKK9wNptw== ARC-Seal: i=1; s=lahtoruutu; d=iki.fi; t=1603959441; a=rsa-sha256; cv=none; b=ermR5QrvIMcAq3K8B0dZGEsfMqYBdY1nWu3Vw+ilDvdUwA0SKjA3DyydrEOUa/1BFhUTZ4 ulLalEX0y2J2tVsQtNeshqV2aG/UoCGrQEjlobLleUnGOpaZX8AI7KhCVj51xBbKK5lm2D elzcDl5YSXIfoY9WSr961jn1pEAzdd3s9M0uNofNIdQFD15ureS5BR3aNG7Fibi6zVLNrH rkH85g2zMV8rfS9ZZaFEzioGxigFCRdNAr+7E1txQC4gjRX0hXQ3ifVTahL8sMJW9wPXnA 6cQN6f5wBKHMBmP6vdsSfPuA95qlQyqYV1gwUlyjl8MLQfsSmpZVP8U9xAx/sw== ARC-Authentication-Results: i=1; ORIGINATING; auth=pass smtp.auth=hlinnaka smtp.mailfrom=hlinnaka@iki.fi List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------778645A61BB63B2AE9F12F88 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit On 28/10/2020 21:59, Andres Freund wrote: > On 2020-10-28 21:10:41 +0200, Heikki Linnakangas wrote: >> Currently, ExecInitAgg() performs quite a lot of work, to deduplicate >> identical Aggrefs, as well as Aggrefs that can share the same transition >> state. That doesn't really belong in the executor, we should perform that >> work in the planner. It doesn't change from one invocation of the plan to >> another, and it would be nice to reflect the state-sharing in the plan >> costs. > > Woo! Very glad to see this tackled. > > It wouldn't surprise me to see a small execution time speedup here - > I've seen the load of the aggno show up in profiles. I think you'd be hard-pressed to find a real-life query where it matters. But if you don't care about real life: regression=# do $$ begin for i in 1..100000 loop perform sum(g), sum(g+0), sum(g+1), sum(g+2), sum(g+3), sum(g+4), sum(g+5), sum(g+6), sum(g+7), sum(g+8), sum(g+9), sum(g+10) from generate_series(1,1) g; end loop; end; $$; DO Time: 1282.701 ms (00:01.283) vs. Time: 860.323 ms with the patch. >> @@ -783,14 +783,13 @@ ExecInitExprRec(Expr *node, ExprState *state, >> >> scratch.opcode = EEOP_AGGREF; >> scratch.d.aggref.astate = astate; >> - astate->aggref = aggref; >> + astate->aggno = aggref->aggno; >> >> if (state->parent && IsA(state->parent, AggState)) >> { >> AggState *aggstate = (AggState *) state->parent; >> >> - aggstate->aggs = lappend(aggstate->aggs, astate); >> - aggstate->numaggs++; >> + aggstate->aggs = lappend(aggstate->aggs, aggref); > > Hm. Why is aggstate->aggs still built during expression initialization? > Imo that's a pretty huge wart that also introduces more > order-of-operation brittleness to executor startup. The Agg node itself doesn't include any information about the aggregates and transition functions. Because of that, ExecInitAgg needs a "representive" Aggref for each transition state and agg, to initialize the per-trans and per-agg structs. The expression initialization makes those Aggrefs available for ExecInitAgg. Instead of collecting all the Aggrefs in a list, ExecInitExprRec() could set each representative Aggref directly in the right per-trans and per-agg struct, based on the 'aggno' and 'aggtransno' fields. That requires initializing the per-trans and per-agg arrays earlier, and for that, we would need to store the # of aggs and transition states in the Agg node, like you also suggested. Certainly doable, but on the whole, it didn't really seem better to me. Attached is a patch, to demonstrate what that looks like, on top of the main patch. It's not complete, there's at least one case with hash-DISTINCT for queries like "SELECT DISTINCT aggregate(x) ..." where the planner creates an Agg for the DISTINCT without aggregates, but the code currently passes numAggs=1 to the executor. Some further changes would be needed in the planner, to mark the AggPath generated for deduplication differently from the AggPaths created for aggregation. Again that's doable, but on the whole I prefer the approach to scan the Aggrefs in executor startup, for now. I'd like to get rid of the "representative Aggrefs" altogether, and pass information about the transition and final functions from planner to executor in some other form. But that's exactly what got me into the refactoring that was ballooning out of hand that I mentioned. - Heikki --------------778645A61BB63B2AE9F12F88 Content-Type: text/x-patch; charset=UTF-8; name="pass-numaggs-from-planner.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="pass-numaggs-from-planner.patch" diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 2a4dea2b052..6a03fa730e5 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -785,11 +785,23 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.d.aggref.astate = astate; astate->aggno = aggref->aggno; + /* + * Remember this Aggref as the representative for the + * aggregate. ExecInitAgg needs a representative for + * initializing the states, and it can also be accessed + * by the user-defined functions by AggGetAggref(). + */ if (state->parent && IsA(state->parent, AggState)) { AggState *aggstate = (AggState *) state->parent; + AggStatePerAgg peragg; + + if (aggref->aggno >= aggstate->numaggs) + elog(ERROR, "invalid aggno %d", aggref->aggno); + peragg = &aggstate->peragg[aggref->aggno]; - aggstate->aggs = lappend(aggstate->aggs, aggref); + if (peragg->aggref == NULL) + peragg->aggref = aggref; } else { diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 7585689b94d..1949729a494 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -3236,13 +3236,11 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) Plan *outerPlan; ExprContext *econtext; TupleDesc scanDesc; - int max_aggno; - int max_transno; - int numaggrefs; int numaggs; int numtrans; int phase; int phaseidx; + int aggno; ListCell *l; Bitmapset *all_grouped_cols = NULL; int numGroupingSets = 1; @@ -3264,7 +3262,6 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) aggstate->ss.ps.state = estate; aggstate->ss.ps.ExecProcNode = ExecAgg; - aggstate->aggs = NIL; aggstate->numaggs = 0; aggstate->numtrans = 0; aggstate->aggstrategy = node->aggstrategy; @@ -3401,6 +3398,17 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) aggstate->ss.ps.outeropsfixed = false; } + /* Initialize the per-agg and per-trans arrays before calling ExecInitExpr() */ + numaggs = node->numAggs; + peraggs = (AggStatePerAgg) palloc0(sizeof(AggStatePerAggData) * numaggs); + aggstate->peragg = peraggs; + aggstate->numaggs = numaggs; + + numtrans = node->numTrans; + pertransstates = (AggStatePerTrans) palloc0(sizeof(AggStatePerTransData) * numtrans); + aggstate->pertrans = pertransstates; + aggstate->numtrans = numtrans; + /* * Initialize result type, slot and projection. */ @@ -3423,22 +3431,6 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) aggstate->ss.ps.qual = ExecInitQual(node->plan.qual, (PlanState *) aggstate); - /* - * We should now have found all Aggrefs in the targetlist and quals. - */ - numaggrefs = list_length(aggstate->aggs); - max_aggno = -1; - max_transno = -1; - foreach(l, aggstate->aggs) - { - Aggref *aggref = (Aggref *) lfirst(l); - - max_aggno = Max(max_aggno, aggref->aggno); - max_transno = Max(max_transno, aggref->aggtransno); - } - numaggs = max_aggno + 1; - numtrans = max_transno + 1; - /* * For each phase, prepare grouping set data and fmgr lookup data for * compare functions. Accumulate all_grouped_cols in passing. @@ -3607,13 +3599,6 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * numaggs); econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * numaggs); - peraggs = (AggStatePerAgg) palloc0(sizeof(AggStatePerAggData) * numaggs); - pertransstates = (AggStatePerTrans) palloc0(sizeof(AggStatePerTransData) * numtrans); - - aggstate->peragg = peraggs; - aggstate->pertrans = pertransstates; - - aggstate->all_pergroups = (AggStatePerGroup *) palloc0(sizeof(AggStatePerGroup) * (numGroupingSets + numHashes)); @@ -3699,11 +3684,11 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) * Perform lookups of aggregate function info, and initialize the * unchanging fields of the per-agg and per-trans data. */ - foreach(l, aggstate->aggs) + for (aggno = 0; aggno < numaggs; aggno++) { - Aggref *aggref = lfirst(l); - AggStatePerAgg peragg; + AggStatePerAgg peragg = &peraggs[aggno]; AggStatePerTrans pertrans; + Aggref *aggref; Oid inputTypes[FUNC_MAX_ARGS]; int numArguments; int numDirectArgs; @@ -3717,18 +3702,18 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) Expr *finalfnexpr; Oid aggtranstype; + if (peragg->aggref == NULL) + { + elog(ERROR, "aggregate %d of %d was not found in expression initialization", + aggno, numaggs); + } + aggref = peragg->aggref; + /* Planner should have assigned aggregate to correct level */ Assert(aggref->agglevelsup == 0); /* ... and the split mode should match */ Assert(aggref->aggsplit == aggstate->aggsplit); - peragg = &peraggs[aggref->aggno]; - - /* Check if we initialized the state for this aggregate already. */ - if (peragg->aggref != NULL) - continue; - - peragg->aggref = aggref; peragg->transno = aggref->aggtransno; /* Fetch the pg_aggregate row */ @@ -3934,13 +3919,6 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) ReleaseSysCache(aggTuple); } - /* - * Update aggstate->numaggs to be the number of unique aggregates found. - * Also set numstates to the number of unique transition states found. - */ - aggstate->numaggs = numaggs; - aggstate->numtrans = numtrans; - /* * Last, check whether any more aggregates got added onto the node while * we processed the expressions for the aggregate arguments (including not @@ -3951,10 +3929,13 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) * need to work hard on a helpful error message; but we defend against it * here anyway, just to be sure.) */ + /* FIXME: need another way to sanity check this now. Not hard. Or just remove this */ +#if 0 if (numaggrefs != list_length(aggstate->aggs)) ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("aggregate function calls cannot be nested"))); +#endif /* * Build expressions doing all the transition work at once. We build a diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d15866de89f..812735576f2 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1024,6 +1024,8 @@ _copyAgg(const Agg *from) COPY_BITMAPSET_FIELD(aggParams); COPY_NODE_FIELD(groupingSets); COPY_NODE_FIELD(chain); + COPY_SCALAR_FIELD(numAggs); + COPY_SCALAR_FIELD(numTrans); return newnode; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3c740b805f3..e3c168531f9 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -785,6 +785,8 @@ _outAgg(StringInfo str, const Agg *node) WRITE_BITMAPSET_FIELD(aggParams); WRITE_NODE_FIELD(groupingSets); WRITE_NODE_FIELD(chain); + WRITE_INT_FIELD(numAggs); + WRITE_INT_FIELD(numTrans); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 169d5581b91..43533e13daa 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -2235,6 +2235,8 @@ _readAgg(void) READ_BITMAPSET_FIELD(aggParams); READ_NODE_FIELD(groupingSets); READ_NODE_FIELD(chain); + READ_INT_FIELD(numAggs); + READ_INT_FIELD(numTrans); READ_DONE(); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 94280a730c4..b5e88acf055 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1657,7 +1657,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags) NIL, NIL, best_path->path.rows, - 0, + 0, 0, 0, subplan); } else @@ -2139,6 +2139,7 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path) NIL, best_path->numGroups, best_path->transitionSpace, + list_length(root->agginfos), list_length(root->aggtransinfos), subplan); copy_generic_path_info(&plan->plan, (Path *) best_path); @@ -2301,6 +2302,8 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) NIL, rollup->numGroups, best_path->transitionSpace, + list_length(root->agginfos), + list_length(root->aggtransinfos), sort_plan); /* @@ -2340,6 +2343,8 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) chain, rollup->numGroups, best_path->transitionSpace, + list_length(root->agginfos), + list_length(root->aggtransinfos), subplan); /* Copy cost data from Path to Plan */ @@ -6350,7 +6355,7 @@ make_agg(List *tlist, List *qual, AggStrategy aggstrategy, AggSplit aggsplit, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations, List *groupingSets, List *chain, double dNumGroups, - Size transitionSpace, Plan *lefttree) + Size transitionSpace, int numAggs, int numTrans, Plan *lefttree) { Agg *node = makeNode(Agg); Plan *plan = &node->plan; @@ -6370,6 +6375,8 @@ make_agg(List *tlist, List *qual, node->aggParams = NULL; /* SS_finalize_plan() will fill this */ node->groupingSets = groupingSets; node->chain = chain; + node->numAggs = numAggs; + node->numTrans = numTrans; plan->qual = qual; plan->targetlist = tlist; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index fc5698cff20..ce47d7869dc 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -2140,7 +2140,6 @@ typedef struct AggStatePerHashData *AggStatePerHash; typedef struct AggState { ScanState ss; /* its first field is NodeTag */ - List *aggs; /* all Aggref nodes in targetlist & quals */ int numaggs; /* length of list (could be zero!) */ int numtrans; /* number of pertrans items */ AggStrategy aggstrategy; /* strategy mode */ @@ -2153,15 +2152,15 @@ typedef struct AggState ExprContext *hashcontext; /* econtexts for long-lived data (hashtable) */ ExprContext **aggcontexts; /* econtexts for long-lived data (per GS) */ ExprContext *tmpcontext; /* econtext for input expressions */ -#define FIELDNO_AGGSTATE_CURAGGCONTEXT 14 +#define FIELDNO_AGGSTATE_CURAGGCONTEXT 13 ExprContext *curaggcontext; /* currently active aggcontext */ AggStatePerAgg curperagg; /* currently active aggregate, if any */ -#define FIELDNO_AGGSTATE_CURPERTRANS 16 +#define FIELDNO_AGGSTATE_CURPERTRANS 15 AggStatePerTrans curpertrans; /* currently active trans state, if any */ bool input_done; /* indicates end of input */ bool agg_done; /* indicates completion of Agg scan */ int projected_set; /* The last projected grouping set */ -#define FIELDNO_AGGSTATE_CURRENT_SET 20 +#define FIELDNO_AGGSTATE_CURRENT_SET 19 int current_set; /* The current grouping set being evaluated */ Bitmapset *grouped_cols; /* grouped cols in current projection */ List *all_grouped_cols; /* list of all grouped cols in DESC order */ @@ -2207,7 +2206,7 @@ typedef struct AggState * per-group pointers */ /* support for evaluation of agg input expressions: */ -#define FIELDNO_AGGSTATE_ALL_PERGROUPS 53 +#define FIELDNO_AGGSTATE_ALL_PERGROUPS 52 AggStatePerGroup *all_pergroups; /* array of first ->pergroups, than * ->hash_pergroup */ ProjectionInfo *combinedproj; /* projection machinery */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 7e6b10f86b9..f60f301a2b1 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -828,6 +828,9 @@ typedef struct Agg /* Note: planner provides numGroups & aggParams only in HASHED/MIXED case */ List *groupingSets; /* grouping sets to use */ List *chain; /* chained Agg/Sort nodes */ + + int numAggs; + int numTrans; } Agg; /* ---------------- diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index f3cefe67b8d..d64f7c1344b 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -55,7 +55,7 @@ extern Agg *make_agg(List *tlist, List *qual, AggStrategy aggstrategy, AggSplit aggsplit, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations, List *groupingSets, List *chain, double dNumGroups, - Size transitionSpace, Plan *lefttree); + Size transitionSpace, int numAggs, int numTrans, Plan *lefttree); extern Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, LimitOption limitOption, int uniqNumCols, AttrNumber *uniqColIdx, Oid *uniqOperators, --------------778645A61BB63B2AE9F12F88--