aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/execExpr.c
diff options
context:
space:
mode:
authorDavid Rowley <drowley@postgresql.org>2024-08-20 13:38:22 +1200
committerDavid Rowley <drowley@postgresql.org>2024-08-20 13:38:22 +1200
commitadf97c1562380e02acd60dc859c289ed3a8352ee (patch)
treebbc199a61078c00d997903c4d5ce0c2fdccc7224 /src/backend/executor/execExpr.c
parent9380e5f129d2a160ecc2444f61bb7cb97fd51fbb (diff)
downloadpostgresql-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.c141
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.