diff options
author | David Rowley <drowley@postgresql.org> | 2024-12-11 13:47:16 +1300 |
---|---|---|
committer | David Rowley <drowley@postgresql.org> | 2024-12-11 13:47:16 +1300 |
commit | 0f5738202b812a976e8612c85399b52d16a0abb6 (patch) | |
tree | 9edf2821d761f7f42615573a45bf93f9ed64cf75 /src/backend/executor/execExpr.c | |
parent | a43567483c617fb046c805b61964d5168c9a0553 (diff) |
Use ExprStates for hashing in GROUP BY and SubPlans
This speeds up obtaining hash values for GROUP BY and hashed SubPlans by
using the ExprState support for hashing, thus allowing JIT compilation for
obtaining hash values for these operations.
This, even without JIT compilation, has been shown to improve Hash
Aggregate performance in some cases by around 15% and hashed NOT IN
queries in one case by over 30%, however, real-world cases are likely to
see smaller gains as the test cases used were purposefully designed to
have high hashing overheads by keeping the hash table small to prevent
additional memory overheads that would be a factor when working with large
hash tables.
In passing, fix a hypothetical bug in ExecBuildHash32Expr() so that the
initial value is stored directly in the ExprState's result field if
there are no expressions to hash. None of the current users of this
function use an initial value, so the bug is only hypothetical.
Reviewed-by: Andrei Lepikhov <lepihov@gmail.com>
Discussion: https://postgr.es/m/CAApHDvpYSO3kc9UryMevWqthTBrxgfd9djiAjKHMPUSQeX9vdQ@mail.gmail.com
Diffstat (limited to 'src/backend/executor/execExpr.c')
-rw-r--r-- | src/backend/executor/execExpr.c | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index e0eb96fd5ad..81714341f07 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -3979,6 +3979,161 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate, } /* + * Build an ExprState that calls the given hash function(s) on the attnums + * given by 'keyColIdx' . When numCols > 1, the hash values returned by each + * hash function are combined to produce a single hash value. + * + * desc: tuple descriptor for the to-be-hashed columns + * ops: TupleTableSlotOps to use for the give TupleDesc + * hashfunctions: FmgrInfos for each hash function to call, one per numCols. + * These are used directly in the returned ExprState so must remain allocated. + * collations: collation to use when calling the hash function. + * numCols: array length of hashfunctions, collations and keyColIdx. + * parent: PlanState node that the resulting ExprState will be evaluated at + * init_value: Normally 0, but can be set to other values to seed the hash + * with. Non-zero is marginally slower, so best to only use if it's provably + * worthwhile. + */ +ExprState * +ExecBuildHash32FromAttrs(TupleDesc desc, const TupleTableSlotOps *ops, + FmgrInfo *hashfunctions, Oid *collations, + int numCols, AttrNumber *keyColIdx, + PlanState *parent, uint32 init_value) +{ + ExprState *state = makeNode(ExprState); + ExprEvalStep scratch = {0}; + NullableDatum *iresult = NULL; + intptr_t opcode; + AttrNumber last_attnum = 0; + + Assert(numCols >= 0); + + state->parent = parent; + + /* + * Make a place to store intermediate hash values between subsequent + * hashing of individual columns. We only need this if there is more than + * one column to hash or an initial value plus one column. + */ + if ((int64) numCols + (init_value != 0) > 1) + iresult = palloc(sizeof(NullableDatum)); + + /* find the highest attnum so we deform the tuple to that point */ + for (int i = 0; i < numCols; i++) + last_attnum = Max(last_attnum, keyColIdx[i]); + + scratch.opcode = EEOP_INNER_FETCHSOME; + scratch.d.fetch.last_var = last_attnum; + scratch.d.fetch.fixed = false; + scratch.d.fetch.kind = ops; + scratch.d.fetch.known_desc = desc; + if (ExecComputeSlotInfo(state, &scratch)) + ExprEvalPushStep(state, &scratch); + + if (init_value == 0) + { + /* + * No initial value, so we can assign the result of the hash function + * for the first attribute without having to concern ourselves with + * combining the result with any initial value. + */ + opcode = EEOP_HASHDATUM_FIRST; + } + else + { + /* + * Set up operation to set the initial value. Normally we store this + * in the intermediate hash value location, but if there are no + * columns to hash, store it in the ExprState's result field. + */ + scratch.opcode = EEOP_HASHDATUM_SET_INITVAL; + scratch.d.hashdatum_initvalue.init_value = UInt32GetDatum(init_value); + scratch.resvalue = numCols > 0 ? &iresult->value : &state->resvalue; + scratch.resnull = numCols > 0 ? &iresult->isnull : &state->resnull; + + ExprEvalPushStep(state, &scratch); + + /* + * When using an initial value use the NEXT32 ops as the FIRST ops + * would overwrite the stored initial value. + */ + opcode = EEOP_HASHDATUM_NEXT32; + } + + for (int i = 0; i < numCols; i++) + { + FmgrInfo *finfo; + FunctionCallInfo fcinfo; + Oid inputcollid = collations[i]; + AttrNumber attnum = keyColIdx[i] - 1; + + finfo = &hashfunctions[i]; + fcinfo = palloc0(SizeForFunctionCallInfo(1)); + + /* Initialize function call parameter structure too */ + InitFunctionCallInfoData(*fcinfo, finfo, 1, inputcollid, NULL, NULL); + + /* + * Fetch inner Var for this attnum and store it in the 1st arg of the + * hash func. + */ + scratch.opcode = EEOP_INNER_VAR; + scratch.resvalue = &fcinfo->args[0].value; + scratch.resnull = &fcinfo->args[0].isnull; + scratch.d.var.attnum = attnum; + scratch.d.var.vartype = TupleDescAttr(desc, attnum)->atttypid; + + ExprEvalPushStep(state, &scratch); + + /* Call the hash function */ + scratch.opcode = opcode; + + if (i == numCols - 1) + { + /* + * The result for hashing the final column is stored in the + * ExprState. + */ + scratch.resvalue = &state->resvalue; + scratch.resnull = &state->resnull; + } + else + { + Assert(iresult != NULL); + + /* intermediate values are stored in an intermediate result */ + scratch.resvalue = &iresult->value; + scratch.resnull = &iresult->isnull; + } + + /* + * NEXT32 opcodes need to look at the intermediate result. We might + * as well just set this for all ops. FIRSTs won't look at it. + */ + scratch.d.hashdatum.iresult = iresult; + + scratch.d.hashdatum.finfo = finfo; + scratch.d.hashdatum.fcinfo_data = fcinfo; + scratch.d.hashdatum.fn_addr = finfo->fn_addr; + scratch.d.hashdatum.jumpdone = -1; + + ExprEvalPushStep(state, &scratch); + + /* subsequent attnums must be combined with the previous */ + opcode = EEOP_HASHDATUM_NEXT32; + } + + scratch.resvalue = NULL; + scratch.resnull = NULL; + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + +/* * Build an ExprState that calls the given hash function(s) on the given * 'hash_exprs'. When multiple expressions are present, the hash values * returned by each hash function are combined to produce a single hash value. |