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/nodeHashjoin.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/nodeHashjoin.c')
-rw-r--r-- | src/backend/executor/nodeHashjoin.c | 143 |
1 files changed, 119 insertions, 24 deletions
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 5429e687342..2f7170604d6 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -169,6 +169,7 @@ #include "executor/nodeHash.h" #include "executor/nodeHashjoin.h" #include "miscadmin.h" +#include "utils/lsyscache.h" #include "utils/sharedtuplestore.h" #include "utils/wait_event.h" @@ -331,10 +332,7 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel) * whoever gets here first will create the hash table and any * later arrivals will merely attach to it. */ - hashtable = ExecHashTableCreate(hashNode, - node->hj_HashOperators, - node->hj_Collations, - HJ_FILL_INNER(node)); + hashtable = ExecHashTableCreate(hashNode); node->hj_HashTable = hashtable; /* @@ -820,9 +818,96 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) */ { HashState *hashstate = (HashState *) innerPlanState(hjstate); + Hash *hash = (Hash *) hashstate->ps.plan; TupleTableSlot *slot = hashstate->ps.ps_ResultTupleSlot; + Oid *outer_hashfuncid; + Oid *inner_hashfuncid; + bool *hash_strict; + ListCell *lc; + int nkeys; + hjstate->hj_HashTupleSlot = slot; + + /* + * Build ExprStates to obtain hash values for either side of the join. + * This must be done here as ExecBuildHash32Expr needs to know how to + * handle NULL inputs and the required handling of that depends on the + * jointype. We don't know the join type in ExecInitHash() and we + * must build the ExprStates before ExecHashTableCreate() so we + * properly attribute any SubPlans that exist in the hash expressions + * to the correct PlanState. + */ + nkeys = list_length(node->hashoperators); + + outer_hashfuncid = palloc_array(Oid, nkeys); + inner_hashfuncid = palloc_array(Oid, nkeys); + hash_strict = palloc_array(bool, nkeys); + + /* + * Determine the hash function for each side of the join for the given + * hash operator. + */ + foreach(lc, node->hashoperators) + { + Oid hashop = lfirst_oid(lc); + int i = foreach_current_index(lc); + + if (!get_op_hash_functions(hashop, + &outer_hashfuncid[i], + &inner_hashfuncid[i])) + elog(ERROR, + "could not find hash function for hash operator %u", + hashop); + hash_strict[i] = op_strict(hashop); + } + + /* + * Build an ExprState to generate the hash value for the expressions + * on the outer of the join. This ExprState must finish generating + * the hash value when HJ_FILL_OUTER() is true. Otherwise, + * ExecBuildHash32Expr will set up the ExprState to abort early if it + * finds a NULL. In these cases, we don't need to store these tuples + * in the hash table as the jointype does not require it. + */ + hjstate->hj_OuterHash = + ExecBuildHash32Expr(hjstate->js.ps.ps_ResultTupleDesc, + hjstate->js.ps.resultops, + outer_hashfuncid, + node->hashcollations, + node->hashkeys, + hash_strict, + &hjstate->js.ps, + 0, + HJ_FILL_OUTER(hjstate)); + + /* As above, but for the inner side of the join */ + hashstate->hash_expr = + ExecBuildHash32Expr(hashstate->ps.ps_ResultTupleDesc, + hashstate->ps.resultops, + inner_hashfuncid, + node->hashcollations, + hash->hashkeys, + hash_strict, + &hashstate->ps, + 0, + HJ_FILL_INNER(hjstate)); + + /* + * Set up the skew table hash function while we have a record of the + * first key's hash function Oid. + */ + if (OidIsValid(hash->skewTable)) + { + hashstate->skew_hashfunction = palloc0(sizeof(FmgrInfo)); + hashstate->skew_collation = linitial_oid(node->hashcollations); + fmgr_info(outer_hashfuncid[0], hashstate->skew_hashfunction); + } + + /* no need to keep these */ + pfree(outer_hashfuncid); + pfree(inner_hashfuncid); + pfree(hash_strict); } /* @@ -846,11 +931,6 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) hjstate->hj_CurSkewBucketNo = INVALID_SKEW_BUCKET_NO; hjstate->hj_CurTuple = NULL; - hjstate->hj_OuterHashKeys = ExecInitExprList(node->hashkeys, - (PlanState *) hjstate); - hjstate->hj_HashOperators = node->hashoperators; - hjstate->hj_Collations = node->hashcollations; - hjstate->hj_JoinState = HJ_BUILD_HASHTABLE; hjstate->hj_MatchedOuter = false; hjstate->hj_OuterNotEmpty = false; @@ -918,17 +998,22 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode, while (!TupIsNull(slot)) { + bool isnull; + /* * We have to compute the tuple's hash value. */ ExprContext *econtext = hjstate->js.ps.ps_ExprContext; econtext->ecxt_outertuple = slot; - if (ExecHashGetHashValue(hashtable, econtext, - hjstate->hj_OuterHashKeys, - true, /* outer tuple */ - HJ_FILL_OUTER(hjstate), - hashvalue)) + + ResetExprContext(econtext); + + *hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash, + econtext, + &isnull)); + + if (!isnull) { /* remember outer relation is not empty for possible rescan */ hjstate->hj_OuterNotEmpty = true; @@ -989,14 +1074,19 @@ ExecParallelHashJoinOuterGetTuple(PlanState *outerNode, while (!TupIsNull(slot)) { + bool isnull; + ExprContext *econtext = hjstate->js.ps.ps_ExprContext; econtext->ecxt_outertuple = slot; - if (ExecHashGetHashValue(hashtable, econtext, - hjstate->hj_OuterHashKeys, - true, /* outer tuple */ - HJ_FILL_OUTER(hjstate), - hashvalue)) + + ResetExprContext(econtext); + + *hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash, + econtext, + &isnull)); + + if (!isnull) return slot; /* @@ -1518,15 +1608,20 @@ ExecParallelHashJoinPartitionOuter(HashJoinState *hjstate) /* Execute outer plan, writing all tuples to shared tuplestores. */ for (;;) { + bool isnull; + slot = ExecProcNode(outerState); if (TupIsNull(slot)) break; econtext->ecxt_outertuple = slot; - if (ExecHashGetHashValue(hashtable, econtext, - hjstate->hj_OuterHashKeys, - true, /* outer tuple */ - HJ_FILL_OUTER(hjstate), - &hashvalue)) + + ResetExprContext(econtext); + + hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash, + econtext, + &isnull)); + + if (!isnull) { int batchno; int bucketno; |