diff options
author | David Rowley <drowley@postgresql.org> | 2024-08-20 13:38:22 +1200 |
---|---|---|
committer | David Rowley <drowley@postgresql.org> | 2024-08-20 13:38:22 +1200 |
commit | adf97c1562380e02acd60dc859c289ed3a8352ee (patch) | |
tree | bbc199a61078c00d997903c4d5ce0c2fdccc7224 /src/backend/executor/execExpr.c | |
parent | 9380e5f129d2a160ecc2444f61bb7cb97fd51fbb (diff) | |
download | postgresql-adf97c1562380e02acd60dc859c289ed3a8352ee.tar.gz postgresql-adf97c1562380e02acd60dc859c289ed3a8352ee.zip |
Speed up Hash Join by making ExprStates support hashing
Here we add ExprState support for obtaining a 32-bit hash value from a
list of expressions. This allows both faster hashing and also JIT
compilation of these expressions. This is especially useful when hash
joins have multiple join keys as the previous code called ExecEvalExpr on
each hash join key individually and that was inefficient as tuple
deformation would have only taken into account one key at a time, which
could lead to walking the tuple once for each join key. With the new
code, we'll determine the maximum attribute required and deform the tuple
to that point only once.
Some performance tests done with this change have shown up to a 20%
performance increase of a query containing a Hash Join without JIT
compilation and up to a 26% performance increase when JIT is enabled and
optimization and inlining were performed by the JIT compiler. The
performance increase with 1 join column was less with a 14% increase
with and without JIT. This test was done using a fairly small hash
table and a large number of hash probes. The increase will likely be
less with large tables, especially ones larger than L3 cache as memory
pressure is more likely to be the limiting factor there.
This commit only addresses Hash Joins, but lays expression evaluation
and JIT compilation infrastructure for other hashing needs such as Hash
Aggregate.
Author: David Rowley
Reviewed-by: Alexey Dvoichenkov <alexey@hyperplane.net>
Reviewed-by: Tels <nospam-pg-abuse@bloodgate.com>
Discussion: https://postgr.es/m/CAApHDvoexAxgQFNQD_GRkr2O_eJUD1-wUGm%3Dm0L%2BGc%3DT%3DkEa4g%40mail.gmail.com
Diffstat (limited to 'src/backend/executor/execExpr.c')
-rw-r--r-- | src/backend/executor/execExpr.c | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 66dda8e5e69..63289ee35ee 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -3970,6 +3970,147 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate, } /* + * 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. + * + * desc: tuple descriptor for the to-be-hashed expressions + * ops: TupleTableSlotOps for the TupleDesc + * hashfunc_oids: Oid for each hash function to call, one for each 'hash_expr' + * collations: collation to use when calling the hash function. + * hash_expr: list of expressions to hash the value of + * opstrict: array corresponding to the 'hashfunc_oids' to store op_strict() + * parent: PlanState node that the 'hash_exprs' will be evaluated at + * init_value: Normally 0, but can be set to other values to seed the hash + * with some other value. Using non-zero is slightly less efficient but can + * be useful. + * keep_nulls: if true, evaluation of the returned ExprState will abort early + * returning NULL if the given hash function is strict and the Datum to hash + * is null. When set to false, any NULL input Datums are skipped. + */ +ExprState * +ExecBuildHash32Expr(TupleDesc desc, const TupleTableSlotOps *ops, + const Oid *hashfunc_oids, const List *collations, + const List *hash_exprs, const bool *opstrict, + PlanState *parent, uint32 init_value, bool keep_nulls) +{ + ExprState *state = makeNode(ExprState); + ExprEvalStep scratch = {0}; + List *adjust_jumps = NIL; + ListCell *lc; + ListCell *lc2; + intptr_t strict_opcode; + intptr_t opcode; + + Assert(list_length(hash_exprs) == list_length(collations)); + + state->parent = parent; + + /* Insert setup steps as needed. */ + ExecCreateExprSetupSteps(state, (Node *) hash_exprs); + + if (init_value == 0) + { + /* + * No initial value, so we can assign the result of the hash function + * for the first hash_expr without having to concern ourselves with + * combining the result with any initial value. + */ + strict_opcode = EEOP_HASHDATUM_FIRST_STRICT; + opcode = EEOP_HASHDATUM_FIRST; + } + else + { + /* Set up operation to set the initial value. */ + scratch.opcode = EEOP_HASHDATUM_SET_INITVAL; + scratch.d.hashdatum_initvalue.init_value = UInt32GetDatum(init_value); + scratch.resvalue = &state->resvalue; + scratch.resnull = &state->resnull; + + ExprEvalPushStep(state, &scratch); + + /* + * When using an initial value use the NEXT32/NEXT32_STRICT ops as the + * FIRST/FIRST_STRICT ops would overwrite the stored initial value. + */ + strict_opcode = EEOP_HASHDATUM_NEXT32_STRICT; + opcode = EEOP_HASHDATUM_NEXT32; + } + + forboth(lc, hash_exprs, lc2, collations) + { + Expr *expr = (Expr *) lfirst(lc); + FmgrInfo *finfo; + FunctionCallInfo fcinfo; + int i = foreach_current_index(lc); + Oid funcid; + Oid inputcollid = lfirst_oid(lc2); + + funcid = hashfunc_oids[i]; + + /* Allocate hash function lookup data. */ + finfo = palloc0(sizeof(FmgrInfo)); + fcinfo = palloc0(SizeForFunctionCallInfo(1)); + + fmgr_info(funcid, finfo); + + /* + * Build the steps to evaluate the hash function's argument have it so + * the value of that is stored in the 0th argument of the hash func. + */ + ExecInitExprRec(expr, + state, + &fcinfo->args[0].value, + &fcinfo->args[0].isnull); + + scratch.resvalue = &state->resvalue; + scratch.resnull = &state->resnull; + + /* Initialize function call parameter structure too */ + InitFunctionCallInfoData(*fcinfo, finfo, 1, inputcollid, NULL, NULL); + + scratch.d.hashdatum.finfo = finfo; + scratch.d.hashdatum.fcinfo_data = fcinfo; + scratch.d.hashdatum.fn_addr = finfo->fn_addr; + + scratch.opcode = opstrict[i] && !keep_nulls ? strict_opcode : opcode; + scratch.d.hashdatum.jumpdone = -1; + + ExprEvalPushStep(state, &scratch); + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); + + /* + * For subsequent keys we must combine the hash value with the + * previous hashes. + */ + strict_opcode = EEOP_HASHDATUM_NEXT32_STRICT; + opcode = EEOP_HASHDATUM_NEXT32; + } + + /* adjust jump targets */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->opcode == EEOP_HASHDATUM_FIRST || + as->opcode == EEOP_HASHDATUM_FIRST_STRICT || + as->opcode == EEOP_HASHDATUM_NEXT32 || + as->opcode == EEOP_HASHDATUM_NEXT32_STRICT); + Assert(as->d.hashdatum.jumpdone == -1); + as->d.hashdatum.jumpdone = state->steps_len; + } + + scratch.resvalue = NULL; + scratch.resnull = NULL; + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + +/* * Build equality expression that can be evaluated using ExecQual(), returning * true if the expression context's inner/outer tuple are NOT DISTINCT. I.e * two nulls match, a null and a not-null don't match. |