v13-0001-Scan-for-unmatched-hash-join-tuples-in-memory-or.patch
text/x-patch
Filename: v13-0001-Scan-for-unmatched-hash-join-tuples-in-memory-or.patch
Type: text/x-patch
Part: 1
Message:
Re: Parallel Full Hash Join
Patch
Format: format-patch
Series: patch v13-0001
Subject: Scan for unmatched hash join tuples in memory order.
| File | + | − |
|---|---|---|
| src/backend/executor/nodeHash.c | 34 | 51 |
| src/include/executor/hashjoin.h | 4 | 0 |
From 0bd32dd1a7e45e95a62f7f587bfba64bed87da28 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Fri, 24 Mar 2023 14:19:07 +1300
Subject: [PATCH v13 1/4] Scan for unmatched hash join tuples in memory order.
In a full/right outer join, we need to scan every tuple in the hash
table to find the ones that were not matched while probing so that we
can emit null-extended inner tuples. Previously we did that in hash
table bucket order, which means that we dereferenced pointers to tuples
that were randomly scattered in memory (ie in an order derived from the
hash of the join key).
Change to a memory-order scan, using the linked list of memory chunks
that hold the tuples. This isn't really being done for performance
reasons (a subject for later work), but it certainly can't be worse than
the previous random order. The goal for now is to harmonize the scan
logic with a subsequent patch that will parallelize full joins.
Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Thomas Munro <thomas.munro@gmail.com>
Discussion: https://postgr.es/m/CA%2BhUKG%2BA6ftXPz4oe92%2Bx8Er%2BxpGZqto70-Q_ERwRaSyA%3DafNg%40mail.gmail.com
---
src/backend/executor/nodeHash.c | 85 +++++++++++++--------------------
src/include/executor/hashjoin.h | 4 ++
2 files changed, 38 insertions(+), 51 deletions(-)
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 748c9b0024..91fd806c97 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -517,6 +517,8 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
hashtable->spaceAllowed * SKEW_HASH_MEM_PERCENT / 100;
hashtable->chunks = NULL;
hashtable->current_chunk = NULL;
+ hashtable->unmatched_scan_chunk = NULL;
+ hashtable->unmatched_scan_idx = 0;
hashtable->parallel_state = state->parallel_state;
hashtable->area = state->ps.state->es_query_dsa;
hashtable->batches = NULL;
@@ -2058,16 +2060,10 @@ ExecParallelScanHashBucket(HashJoinState *hjstate,
void
ExecPrepHashTableForUnmatched(HashJoinState *hjstate)
{
- /*----------
- * During this scan we use the HashJoinState fields as follows:
- *
- * hj_CurBucketNo: next regular bucket to scan
- * hj_CurSkewBucketNo: next skew bucket (an index into skewBucketNums)
- * hj_CurTuple: last tuple returned, or NULL to start next bucket
- *----------
- */
- hjstate->hj_CurBucketNo = 0;
- hjstate->hj_CurSkewBucketNo = 0;
+ HashJoinTable hashtable = hjstate->hj_HashTable;
+
+ hashtable->unmatched_scan_chunk = hashtable->chunks;
+ hashtable->unmatched_scan_idx = 0;
hjstate->hj_CurTuple = NULL;
}
@@ -2083,58 +2079,45 @@ bool
ExecScanHashTableForUnmatched(HashJoinState *hjstate, ExprContext *econtext)
{
HashJoinTable hashtable = hjstate->hj_HashTable;
- HashJoinTuple hashTuple = hjstate->hj_CurTuple;
+ HashMemoryChunk chunk;
- for (;;)
+ while ((chunk = hashtable->unmatched_scan_chunk))
{
- /*
- * hj_CurTuple is the address of the tuple last returned from the
- * current bucket, or NULL if it's time to start scanning a new
- * bucket.
- */
- if (hashTuple != NULL)
- hashTuple = hashTuple->next.unshared;
- else if (hjstate->hj_CurBucketNo < hashtable->nbuckets)
- {
- hashTuple = hashtable->buckets.unshared[hjstate->hj_CurBucketNo];
- hjstate->hj_CurBucketNo++;
- }
- else if (hjstate->hj_CurSkewBucketNo < hashtable->nSkewBuckets)
+ while (hashtable->unmatched_scan_idx < chunk->used)
{
- int j = hashtable->skewBucketNums[hjstate->hj_CurSkewBucketNo];
+ HashJoinTuple hashTuple = (HashJoinTuple)
+ (HASH_CHUNK_DATA(hashtable->unmatched_scan_chunk) +
+ hashtable->unmatched_scan_idx);
- hashTuple = hashtable->skewBucket[j]->tuples;
- hjstate->hj_CurSkewBucketNo++;
- }
- else
- break; /* finished all buckets */
+ MinimalTuple tuple = HJTUPLE_MINTUPLE(hashTuple);
+ int hashTupleSize = (HJTUPLE_OVERHEAD + tuple->t_len);
- while (hashTuple != NULL)
- {
- if (!HeapTupleHeaderHasMatch(HJTUPLE_MINTUPLE(hashTuple)))
- {
- TupleTableSlot *inntuple;
+ /* next tuple in this chunk */
+ hashtable->unmatched_scan_idx += MAXALIGN(hashTupleSize);
- /* insert hashtable's tuple into exec slot */
- inntuple = ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(hashTuple),
- hjstate->hj_HashTupleSlot,
- false); /* do not pfree */
- econtext->ecxt_innertuple = inntuple;
+ if (HeapTupleHeaderHasMatch(HJTUPLE_MINTUPLE(hashTuple)))
+ continue;
- /*
- * Reset temp memory each time; although this function doesn't
- * do any qual eval, the caller will, so let's keep it
- * parallel to ExecScanHashBucket.
- */
- ResetExprContext(econtext);
+ /* insert hashtable's tuple into exec slot */
+ econtext->ecxt_innertuple =
+ ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(hashTuple),
+ hjstate->hj_HashTupleSlot,
+ false);
- hjstate->hj_CurTuple = hashTuple;
- return true;
- }
+ /*
+ * Reset temp memory each time; although this function doesn't do
+ * any qual eval, the caller will, so let's keep it parallel to
+ * ExecScanHashBucket.
+ */
+ ResetExprContext(econtext);
- hashTuple = hashTuple->next.unshared;
+ hjstate->hj_CurTuple = hashTuple;
+ return true;
}
+ hashtable->unmatched_scan_chunk = chunk->next.unshared;
+ hashtable->unmatched_scan_idx = 0;
+
/* allow this loop to be cancellable */
CHECK_FOR_INTERRUPTS();
}
diff --git a/src/include/executor/hashjoin.h b/src/include/executor/hashjoin.h
index acb7592ca0..0abd888d1e 100644
--- a/src/include/executor/hashjoin.h
+++ b/src/include/executor/hashjoin.h
@@ -352,6 +352,10 @@ typedef struct HashJoinTableData
/* used for dense allocation of tuples (into linked chunks) */
HashMemoryChunk chunks; /* one list for the whole batch */
+ /* current position in unmatched scan (full/right join only) */
+ HashMemoryChunk unmatched_scan_chunk;
+ size_t unmatched_scan_idx;
+
/* Shared and private state for Parallel Hash. */
HashMemoryChunk current_chunk; /* this backend's current chunk */
dsa_area *area; /* DSA area to allocate memory from */
--
2.39.2