/* ---------------------------------------------------------------- *ExecHash * *stubforproformacompliance *----------------------------------------------------------------
*/ static TupleTableSlot *
ExecHash(PlanState *pstate)
{
elog(ERROR, "Hash node does not support ExecProcNode call convention"); return NULL;
}
/* ---------------------------------------------------------------- *MultiExecHash * *buildhashtableforhashjoin,doingpartitioningifmore *thanonebatchisrequired. *----------------------------------------------------------------
*/
Node *
MultiExecHash(HashState *node)
{ /* must provide our own instrumentation support */ if (node->ps.instrument)
InstrStartNode(node->ps.instrument);
if (node->parallel_state != NULL)
MultiExecParallelHash(node); else
MultiExecPrivateHash(node);
/* must provide our own instrumentation support */ if (node->ps.instrument)
InstrStopNode(node->ps.instrument, node->hashtable->partialTuples);
if (!isnull)
{
uint32 hashvalue = DatumGetUInt32(hashdatum); int bucketNumber;
bucketNumber = ExecHashGetSkewBucket(hashtable, hashvalue); if (bucketNumber != INVALID_SKEW_BUCKET_NO)
{ /* It's a skew tuple, so put it into that hash table */
ExecHashSkewTableInsert(hashtable, slot, hashvalue,
bucketNumber);
hashtable->skewTuples += 1;
} else
{ /* Not subject to skew optimization, so insert normally */
ExecHashTableInsert(hashtable, slot, hashvalue);
}
hashtable->totalTuples += 1;
}
}
/* resize the hash table if needed (NTUP_PER_BUCKET exceeded) */ if (hashtable->nbuckets != hashtable->nbuckets_optimal)
ExecHashIncreaseNumBuckets(hashtable);
/* Account for the buckets in spaceUsed (reported in EXPLAIN ANALYZE) */
hashtable->spaceUsed += hashtable->nbuckets * sizeof(HashJoinTuple); if (hashtable->spaceUsed > hashtable->spacePeak)
hashtable->spacePeak = hashtable->spaceUsed;
if (!isnull)
ExecParallelHashTableInsert(hashtable, slot, hashvalue);
hashtable->partialTuples++;
}
/* *Makesurethatanytupleswewrotetodiskarevisibleto *othersbeforeanyonetriestoloadthem.
*/ for (i = 0; i < hashtable->nbatch; ++i)
sts_end_write(hashtable->batches[i].inner_tuples);
void
ExecChooseHashTableSize(double ntuples, int tupwidth, bool useskew, bool try_combined_hash_mem, int parallel_workers,
size_t *space_allowed, int *numbuckets, int *numbatches, int *num_skew_mcvs)
{ int tupsize; double inner_rel_bytes;
size_t hash_table_bytes;
size_t bucket_bytes;
size_t max_pointers; int nbatch = 1; int nbuckets; double dbuckets;
/* Force a plausible relation size if no info */ if (ntuples <= 0.0)
ntuples = 1000.0;
/* Now clamp to integer range */
skew_mcvs = Min(skew_mcvs, INT_MAX);
*num_skew_mcvs = (int) skew_mcvs;
/* Reduce hash_table_bytes by the amount needed for the skew table */ if (skew_mcvs > 0)
hash_table_bytes -= skew_mcvs * bytes_per_mcv;
} else
*num_skew_mcvs = 0;
/* *SetnbucketstoachieveanaveragebucketloadofNTUP_PER_BUCKETwhen *memoryisfilled,assumingasinglebatch;butlimitthevaluesothat *thepointerarrayswe'lltrytoallocatedonotexceedhash_table_bytes *norMaxAllocSize. * *Notethatbothnbucketsandnbatchmustbepowersof2tomake *ExecHashGetBucketAndBatchfast.
*/
max_pointers = hash_table_bytes / sizeof(HashJoinTuple);
max_pointers = Min(max_pointers, MaxAllocSize / sizeof(HashJoinTuple)); /* If max_pointers isn't a power of 2, must round it down to one */
max_pointers = pg_prevpower2_size_t(max_pointers);
/* Also ensure we avoid integer overflow in nbatch and nbuckets */ /* (this step is redundant given the current value of MaxAllocSize) */
max_pointers = Min(max_pointers, INT_MAX / 2 + 1);
dbuckets = ceil(ntuples / NTUP_PER_BUCKET);
dbuckets = Min(dbuckets, max_pointers);
nbuckets = (int) dbuckets; /* don't let nbuckets be really small, though ... */
nbuckets = Max(nbuckets, 1024); /* ... and force it to be a power of 2. */
nbuckets = pg_nextpower2_32(nbuckets);
/* *Ifthere'snotenoughspacetostoretheprojectednumberoftuplesand *therequiredbucketheaders,wewillneedmultiplebatches.
*/
bucket_bytes = sizeof(HashJoinTuple) * nbuckets; if (inner_rel_bytes + bucket_bytes > hash_table_bytes)
{ /* We'll need multiple batches */
size_t sbuckets; double dbatch; int minbatch;
size_t bucket_size;
/* *Makesureallthetempfilesareclosed.Weskipbatch0,sinceit *can'thaveanytempfiles(andthearraysmightnotevenexistif *nbatchisonly1).Parallelhashjoinsdon'tusethesefiles.
*/ if (hashtable->innerBatchFile != NULL)
{ for (i = 1; i < hashtable->nbatch; i++)
{ if (hashtable->innerBatchFile[i])
BufFileClose(hashtable->innerBatchFile[i]); if (hashtable->outerBatchFile[i])
BufFileClose(hashtable->outerBatchFile[i]);
}
}
/* Release working memory (batchCxt is a child, so it goes away too) */
MemoryContextDelete(hashtable->hashCxt);
/* And drop the control block */
pfree(hashtable);
}
/* *ExecHashIncreaseNumBatches *increasetheoriginalnumberofbatchesinordertoreduce *currentmemoryconsumption
*/ staticvoid
ExecHashIncreaseNumBatches(HashJoinTable hashtable)
{ int oldnbatch = hashtable->nbatch; int curbatch = hashtable->curbatch; int nbatch; long ninmemory; long nfreed;
HashMemoryChunk oldchunks;
/* do nothing if we've decided to shut off growth */ if (!hashtable->growEnabled) return;
/* safety check to avoid overflow */ if (oldnbatch > Min(INT_MAX / 2, MaxAllocSize / (sizeof(void *) * 2))) return;
/* consider increasing size of the in-memory hash table instead */ if (ExecHashIncreaseBatchSize(hashtable)) return;
nbatch = oldnbatch * 2;
Assert(nbatch > 1);
#ifdef HJDEBUG
printf("Hashjoin %p: increasing nbatch to %d because space = %zu\n",
hashtable, nbatch, hashtable->spaceUsed); #endif
if (hashtable->innerBatchFile == NULL)
{
MemoryContext oldcxt = MemoryContextSwitchTo(hashtable->spillCxt);
/* we had no file arrays before */
hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch);
hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch);
MemoryContextSwitchTo(oldcxt);
/* time to establish the temp tablespaces, too */
PrepareTempTablespaces();
} else
{ /* enlarge arrays and zero out added entries */
hashtable->innerBatchFile = repalloc0_array(hashtable->innerBatchFile, BufFile *, oldnbatch, nbatch);
hashtable->outerBatchFile = repalloc0_array(hashtable->outerBatchFile, BufFile *, oldnbatch, nbatch);
}
/* If know we need to resize nbuckets, we can do it while rebatching. */ if (hashtable->nbuckets_optimal != hashtable->nbuckets)
{ /* we never decrease the number of buckets */
Assert(hashtable->nbuckets_optimal > hashtable->nbuckets);
/* so, let's scan through the old chunks, and all tuples in each chunk */ while (oldchunks != NULL)
{
HashMemoryChunk nextchunk = oldchunks->next.unshared;
/* position within the buffer (up to oldchunks->used) */
size_t idx = 0;
/* process all tuples stored in this chunk (and then free it) */ while (idx < oldchunks->used)
{
HashJoinTuple hashTuple = (HashJoinTuple) (HASH_CHUNK_DATA(oldchunks) + idx);
MinimalTuple tuple = HJTUPLE_MINTUPLE(hashTuple); int hashTupleSize = (HJTUPLE_OVERHEAD + tuple->t_len); int bucketno; int batchno;
/* and add it back to the appropriate bucket */
copyTuple->next.unshared = hashtable->buckets.unshared[bucketno];
hashtable->buckets.unshared[bucketno] = copyTuple;
} else
{ /* dump it out */
Assert(batchno > curbatch);
ExecHashJoinSaveTuple(HJTUPLE_MINTUPLE(hashTuple),
hashTuple->hashvalue,
&hashtable->innerBatchFile[batchno],
hashtable);
/* *It'sunlikely,butweneedtobepreparedfornewparticipantstoshow *upwhilewe'reinthemiddleofthisoperationsoweneedtoswitchon *barrierphasehere.
*/ switch (PHJ_GROW_BATCHES_PHASE(BarrierPhase(&pstate->grow_batches_barrier)))
{ case PHJ_GROW_BATCHES_ELECT:
/* *Electoneparticipanttopreparetogrowthenumberofbatches. *Thisinvolvesreallocatingorresettingthebucketsofbatch0 *inpreparationforallparticipantstobeginrepartitioningthe *tuples.
*/ if (BarrierArriveAndWait(&pstate->grow_batches_barrier,
WAIT_EVENT_HASH_GROW_BATCHES_ELECT))
{
dsa_pointer_atomic *buckets;
ParallelHashJoinBatch *old_batch0; int new_nbatch; int i;
/* Move the old batch out of the way. */
old_batch0 = hashtable->batches[0].shared;
pstate->old_batches = pstate->batches;
pstate->old_nbatch = hashtable->nbatch;
pstate->batches = InvalidDsaPointer;
/* Free this backend's old accessors. */
ExecParallelHashCloseBatchAccessors(hashtable);
/* Figure out how many batches to use. */ if (hashtable->nbatch == 1)
{ /* *Wearegoingfromsingle-batchtomulti-batch.Weneed *toswitchfromonelargecombinedmemorybudgettothe *regularhash_membudget.
*/
pstate->space_allowed = get_hash_memory_limit();
/* Move all chunks to the work queue for parallel processing. */
pstate->chunk_work_queue = old_batch0->chunks;
/* Disable further growth temporarily while we're growing. */
pstate->growth = PHJ_GROWTH_DISABLED;
} else
{ /* All other participants just flush their tuples to disk. */
ExecParallelHashCloseBatchAccessors(hashtable);
} /* Fall through. */
case PHJ_GROW_BATCHES_REALLOCATE: /* Wait for the above to be finished. */
BarrierArriveAndWait(&pstate->grow_batches_barrier,
WAIT_EVENT_HASH_GROW_BATCHES_REALLOCATE); /* Fall through. */
case PHJ_GROW_BATCHES_REPARTITION: /* Make sure that we have the current dimensions and buckets. */
ExecParallelHashEnsureBatchAccessors(hashtable);
ExecParallelHashTableSetCurrentBatch(hashtable, 0); /* Then partition, flush counters. */
ExecParallelHashRepartitionFirst(hashtable);
ExecParallelHashRepartitionRest(hashtable);
ExecParallelHashMergeCounters(hashtable); /* Wait for the above to be finished. */
BarrierArriveAndWait(&pstate->grow_batches_barrier,
WAIT_EVENT_HASH_GROW_BATCHES_REPARTITION); /* Fall through. */
/* Make sure that we have the current dimensions and buckets. */
ExecParallelHashEnsureBatchAccessors(hashtable);
ExecParallelHashTableSetCurrentBatch(hashtable, 0);
/* Are any of the new generation of batches exhausted? */ for (int i = 0; i < hashtable->nbatch; ++i)
{
ParallelHashJoinBatch *batch;
ParallelHashJoinBatch *old_batch; int parent;
/* Don't keep growing if it's not helping or we'd overflow. */ if (extreme_skew_detected || hashtable->nbatch >= INT_MAX / 2)
pstate->growth = PHJ_GROWTH_DISABLED; elseif (space_exhausted)
pstate->growth = PHJ_GROWTH_NEED_MORE_BATCHES; else
pstate->growth = PHJ_GROWTH_OK;
/* Free the old batches in shared memory. */
dsa_free(hashtable->area, pstate->old_batches);
pstate->old_batches = InvalidDsaPointer;
} /* Fall through. */
case PHJ_GROW_BATCHES_FINISH: /* Wait for the above to complete. */
BarrierArriveAndWait(&pstate->grow_batches_barrier,
WAIT_EVENT_HASH_GROW_BATCHES_FINISH);
}
}
Assert(batchno < hashtable->nbatch); if (batchno == 0)
{ /* It still belongs in batch 0. Copy to a new chunk. */
copyTuple =
ExecParallelHashTupleAlloc(hashtable,
HJTUPLE_OVERHEAD + tuple->t_len,
&shared);
copyTuple->hashvalue = hashTuple->hashvalue;
memcpy(HJTUPLE_MINTUPLE(copyTuple), tuple, tuple->t_len);
ExecParallelHashPushTuple(&hashtable->buckets.shared[bucketno],
copyTuple, shared);
} else
{
size_t tuple_size =
MAXALIGN(HJTUPLE_OVERHEAD + tuple->t_len);
/* It belongs in a later batch. */
hashtable->batches[batchno].estimated_size += tuple_size;
sts_puttuple(hashtable->batches[batchno].inner_tuples,
&hashTuple->hashvalue, tuple);
}
/* Count this tuple. */
++hashtable->batches[0].old_ntuples;
++hashtable->batches[batchno].ntuples;
/* Join in the effort to repartition them. */ for (i = 1; i < old_nbatch; ++i)
{
MinimalTuple tuple;
uint32 hashvalue;
/* Scan one partition from the previous generation. */
sts_begin_parallel_scan(old_inner_tuples[i]); while ((tuple = sts_parallel_scan_next(old_inner_tuples[i], &hashvalue)))
{
size_t tuple_size = MAXALIGN(HJTUPLE_OVERHEAD + tuple->t_len); int bucketno; int batchno;
/* Decide which partition it goes to in the new generation. */
ExecHashGetBucketAndBatch(hashtable, hashvalue, &bucketno,
&batchno);
/* scan through all tuples in all chunks to rebuild the hash table */ for (chunk = hashtable->chunks; chunk != NULL; chunk = chunk->next.unshared)
{ /* process all tuples stored in this chunk */
size_t idx = 0;
while (idx < chunk->used)
{
HashJoinTuple hashTuple = (HashJoinTuple) (HASH_CHUNK_DATA(chunk) + idx); int bucketno; int batchno;
/* add the tuple to the proper bucket */
hashTuple->next.unshared = hashtable->buckets.unshared[bucketno];
hashtable->buckets.unshared[bucketno] = hashTuple;
/* advance index past the tuple */
idx += MAXALIGN(HJTUPLE_OVERHEAD +
HJTUPLE_MINTUPLE(hashTuple)->t_len);
}
/* allow this loop to be cancellable */
CHECK_FOR_INTERRUPTS();
}
}
/* *It'sunlikely,butweneedtobepreparedfornewparticipantstoshow *upwhilewe'reinthemiddleofthisoperationsoweneedtoswitchon *barrierphasehere.
*/ switch (PHJ_GROW_BUCKETS_PHASE(BarrierPhase(&pstate->grow_buckets_barrier)))
{ case PHJ_GROW_BUCKETS_ELECT: /* Elect one participant to prepare to increase nbuckets. */ if (BarrierArriveAndWait(&pstate->grow_buckets_barrier,
WAIT_EVENT_HASH_GROW_BUCKETS_ELECT))
{
size_t size;
dsa_pointer_atomic *buckets;
/* Double the size of the bucket array. */
pstate->nbuckets *= 2;
size = pstate->nbuckets * sizeof(dsa_pointer_atomic);
hashtable->batches[0].shared->size += size / 2;
dsa_free(hashtable->area, hashtable->batches[0].shared->buckets);
hashtable->batches[0].shared->buckets =
dsa_allocate(hashtable->area, size);
buckets = (dsa_pointer_atomic *)
dsa_get_address(hashtable->area,
hashtable->batches[0].shared->buckets); for (i = 0; i < pstate->nbuckets; ++i)
dsa_pointer_atomic_init(&buckets[i], InvalidDsaPointer);
/* Put the chunk list onto the work queue. */
pstate->chunk_work_queue = hashtable->batches[0].shared->chunks;
/* Clear the flag. */
pstate->growth = PHJ_GROWTH_OK;
} /* Fall through. */
case PHJ_GROW_BUCKETS_REALLOCATE: /* Wait for the above to complete. */
BarrierArriveAndWait(&pstate->grow_buckets_barrier,
WAIT_EVENT_HASH_GROW_BUCKETS_REALLOCATE); /* Fall through. */
case PHJ_GROW_BUCKETS_REINSERT: /* Reinsert all tuples into the hash table. */
ExecParallelHashEnsureBatchAccessors(hashtable);
ExecParallelHashTableSetCurrentBatch(hashtable, 0); while ((chunk = ExecParallelHashPopChunkQueue(hashtable, &chunk_s)))
{
size_t idx = 0;
while (idx < chunk->used)
{
HashJoinTuple hashTuple = (HashJoinTuple) (HASH_CHUNK_DATA(chunk) + idx);
dsa_pointer shared = chunk_s + HASH_CHUNK_HEADER_SIZE + idx; int bucketno; int batchno;
/* add the tuple to the proper bucket */
ExecParallelHashPushTuple(&hashtable->buckets.shared[bucketno],
hashTuple, shared);
/* advance index past the tuple */
idx += MAXALIGN(HJTUPLE_OVERHEAD +
HJTUPLE_MINTUPLE(hashTuple)->t_len);
}
/* allow this loop to be cancellable */
CHECK_FOR_INTERRUPTS();
}
BarrierArriveAndWait(&pstate->grow_buckets_barrier,
WAIT_EVENT_HASH_GROW_BUCKETS_REINSERT);
}
}
/* Push it onto the front of the bucket's list */
hashTuple->next.unshared = hashtable->buckets.unshared[bucketno];
hashtable->buckets.unshared[bucketno] = hashTuple;
/* Account for space used, and back off if we've used too much */
hashtable->spaceUsed += hashTupleSize; if (hashtable->spaceUsed > hashtable->spacePeak)
hashtable->spacePeak = hashtable->spaceUsed; if (hashtable->spaceUsed +
hashtable->nbuckets_optimal * sizeof(HashJoinTuple)
> hashtable->spaceAllowed)
ExecHashIncreaseNumBatches(hashtable);
} else
{ /* *putthetupleintoatempfileforlaterbatches
*/
Assert(batchno > hashtable->curbatch);
ExecHashJoinSaveTuple(tuple,
hashvalue,
&hashtable->innerBatchFile[batchno],
hashtable);
}
/* Try to load it into memory. */
Assert(BarrierPhase(&hashtable->parallel_state->build_barrier) ==
PHJ_BUILD_HASH_INNER);
hashTuple = ExecParallelHashTupleAlloc(hashtable,
HJTUPLE_OVERHEAD + tuple->t_len,
&shared); if (hashTuple == NULL) goto retry;
/* Store the hash value in the HashJoinTuple header. */
hashTuple->hashvalue = hashvalue;
memcpy(HJTUPLE_MINTUPLE(hashTuple), tuple, tuple->t_len);
HeapTupleHeaderClearMatch(HJTUPLE_MINTUPLE(hashTuple));
/* Push it onto the front of the bucket's list */
ExecParallelHashPushTuple(&hashtable->buckets.shared[bucketno],
hashTuple, shared);
} else
{
size_t tuple_size = MAXALIGN(HJTUPLE_OVERHEAD + tuple->t_len);
Assert(batchno > 0);
/* Try to preallocate space in the batch if necessary. */ if (hashtable->batches[batchno].preallocated < tuple_size)
{ if (!ExecParallelHashTuplePrealloc(hashtable, batchno, tuple_size)) goto retry;
}
while (hashTuple != NULL)
{ if (hashTuple->hashvalue == hashvalue)
{
TupleTableSlot *inntuple;
/* insert hashtable's tuple into exec slot so ExecQual sees it */
inntuple = ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(hashTuple),
hjstate->hj_HashTupleSlot, false); /* do not pfree */
econtext->ecxt_innertuple = inntuple;
if (ExecQualAndReset(hjclauses, econtext))
{
hjstate->hj_CurTuple = hashTuple; returntrue;
}
}
while (hashTuple != NULL)
{ if (hashTuple->hashvalue == hashvalue)
{
TupleTableSlot *inntuple;
/* insert hashtable's tuple into exec slot so ExecQual sees it */
inntuple = ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(hashTuple),
hjstate->hj_HashTupleSlot, false); /* do not pfree */
econtext->ecxt_innertuple = inntuple;
if (ExecQualAndReset(hjclauses, econtext))
{
hjstate->hj_CurTuple = hashTuple; returntrue;
}
}
/* *Itwouldnotbedeadlock-freetowaitonthebatchbarrier,becauseit *isinPHJ_BATCH_PROBEphase,andthusprocessesattachedtoithave *alreadyemittedtuples.Therefore,we'llholdawait-freeelection: *onlyoneprocesscancontinuetothenextphase,andallothersdetach *fromthisbatch.Theycanstillgoanyworkonotherbatches,ifthere *areany.
*/ if (!BarrierArriveAndDetachExceptLast(&batch->batch_barrier))
{ /* This process considers the batch to be done. */
hashtable->batches[hashtable->curbatch].done = true;
/* Make sure any temporary files are closed. */
sts_end_parallel_scan(hashtable->batches[curbatch].inner_tuples);
sts_end_parallel_scan(hashtable->batches[curbatch].outer_tuples);
/* Reset all flags in the main table ... */ for (i = 0; i < hashtable->nbuckets; i++)
{ for (tuple = hashtable->buckets.unshared[i]; tuple != NULL;
tuple = tuple->next.unshared)
HeapTupleHeaderClearMatch(HJTUPLE_MINTUPLE(tuple));
}
/* ... and the same for the skew buckets, if any */ for (i = 0; i < hashtable->nSkewBuckets; i++)
{ int j = hashtable->skewBucketNums[i];
HashSkewBucket *skewBucket = hashtable->skewBucket[j];
/* Do nothing if planner didn't identify the outer relation's join key */ if (!OidIsValid(node->skewTable)) return; /* Also, do nothing if we don't have room for at least one skew bucket */ if (mcvsToUse <= 0) return;
if (get_attstatsslot(&sslot, statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))
{ double frac; int nbuckets; int i;
if (mcvsToUse > sslot.nvalues)
mcvsToUse = sslot.nvalues;
/* *Calculatetheexpectedfractionofouterrelationthatwill *participateintheskewoptimization.Ifthisisn'tatleast *SKEW_MIN_OUTER_FRACTION,don'tuseskewoptimization.
*/
frac = 0; for (i = 0; i < mcvsToUse; i++)
frac += sslot.numbers[i]; if (frac < SKEW_MIN_OUTER_FRACTION)
{
free_attstatsslot(&sslot);
ReleaseSysCache(statsTuple); return;
}
/* *Okay,setuptheskewhashtable. * *skewBucket[]isanopenaddressinghashtablewithapowerof2size *thatisgreaterthanthenumberofMCVvalues.(Thisensuresthere *willbeatleastonenullentry,sosearcheswillalways *terminate.) * *Note:thiscodecouldfailifmcvsToUseexceedsINT_MAX/8or *MaxAllocSize/sizeof(void*)/8,butthatisnotcurrentlypossible *sincewelimitpg_statisticentriestomuchlessthanthat.
*/
nbuckets = pg_nextpower2_32(mcvsToUse + 1); /* use two more bits just to help avoid collisions */
nbuckets <<= 2;
/* Push it onto the front of the skew bucket's list */
hashTuple->next.unshared = hashtable->skewBucket[bucketNumber]->tuples;
hashtable->skewBucket[bucketNumber]->tuples = hashTuple;
Assert(hashTuple != hashTuple->next.unshared);
/* Account for space used, and back off if we've used too much */
hashtable->spaceUsed += hashTupleSize;
hashtable->spaceUsedSkew += hashTupleSize; if (hashtable->spaceUsed > hashtable->spacePeak)
hashtable->spacePeak = hashtable->spaceUsed; while (hashtable->spaceUsedSkew > hashtable->spaceAllowedSkew)
ExecHashRemoveNextSkewBucket(hashtable);
/* Check we are not over the total spaceAllowed, either */ if (hashtable->spaceUsed > hashtable->spaceAllowed)
ExecHashIncreaseNumBatches(hashtable);
if (shouldFree)
heap_free_minimal_tuple(tuple);
}
/* *ExecHashRemoveNextSkewBucket * *Removetheleastvaluableskewbucketbypushingitstuplesinto *themainhashtable.
*/ staticvoid
ExecHashRemoveNextSkewBucket(HashJoinTable hashtable)
{ int bucketToRemove;
HashSkewBucket *bucket;
uint32 hashvalue; int bucketno; int batchno;
HashJoinTuple hashTuple;
/* Locate the bucket to remove */
bucketToRemove = hashtable->skewBucketNums[hashtable->nSkewBuckets - 1];
bucket = hashtable->skewBucket[bucketToRemove];
/* Process all tuples in the bucket */
hashTuple = bucket->tuples; while (hashTuple != NULL)
{
HashJoinTuple nextHashTuple = hashTuple->next.unshared;
MinimalTuple tuple;
Size tupleSize;
/* Decide whether to put the tuple in the hash table or a temp file */ if (batchno == hashtable->curbatch)
{ /* Move the tuple to the main hash table */
HashJoinTuple copyTuple;
/* We have reduced skew space, but overall space doesn't change */
hashtable->spaceUsedSkew -= tupleSize;
} else
{ /* Put the tuple into a temp file for later batches */
Assert(batchno > hashtable->curbatch);
ExecHashJoinSaveTuple(tuple, hashvalue,
&hashtable->innerBatchFile[batchno],
hashtable);
pfree(hashTuple);
hashtable->spaceUsed -= tupleSize;
hashtable->spaceUsedSkew -= tupleSize;
}
hashTuple = nextHashTuple;
/* allow this loop to be cancellable */
CHECK_FOR_INTERRUPTS();
}
/* *CollectEXPLAINstatsifneeded,savingthemintoDSMmemoryif *ExecHashInitializeWorkerwascalled,orlocalstorageifnot.Inthe *parallelcase,thismustbedoneinExecShutdownHash()ratherthan *ExecEndHash()becausethelatterrunsafterwe'vedetachedfromtheDSM *segment.
*/ void
ExecShutdownHash(HashState *node)
{ /* Allocate save space if EXPLAIN'ing and we didn't do so already */ if (node->ps.instrument && !node->hinstrument)
node->hinstrument = palloc0_object(HashInstrumentation); /* Now accumulate data for the current (final) hash table */ if (node->hinstrument && node->hashtable)
ExecHashAccumInstrumentation(node->hinstrument, node->hashtable);
}
/* just in case the size is not already aligned properly */
size = MAXALIGN(size);
/* *Iftuplesizeislargerthanthreshold,allocateaseparatechunk.
*/ if (size > HASH_CHUNK_THRESHOLD)
{ /* allocate new chunk and put it at the beginning of the list */
newChunk = (HashMemoryChunk) MemoryContextAlloc(hashtable->batchCxt,
HASH_CHUNK_HEADER_SIZE + size);
newChunk->maxlen = size;
newChunk->used = size;
newChunk->ntuples = 1;
/* *Seeifwehaveenoughspaceforitinthecurrentchunk(ifany).If *not,allocateafreshchunk.
*/ if ((hashtable->chunks == NULL) ||
(hashtable->chunks->maxlen - hashtable->chunks->used) < size)
{ /* allocate new chunk and put it at the beginning of the list */
newChunk = (HashMemoryChunk) MemoryContextAlloc(hashtable->batchCxt,
HASH_CHUNK_HEADER_SIZE + HASH_CHUNK_SIZE);
/* There is enough space in the current chunk, let's add the tuple */
ptr = HASH_CHUNK_DATA(hashtable->chunks) + hashtable->chunks->used;
hashtable->chunks->used += size;
hashtable->chunks->ntuples += 1;
/* return pointer to the start of the tuple memory */ return ptr;
}
/* Another participant has commanded us to help grow. */ if (growth == PHJ_GROWTH_NEED_MORE_BATCHES)
ExecParallelHashIncreaseNumBatches(hashtable); elseif (growth == PHJ_GROWTH_NEED_MORE_BUCKETS)
ExecParallelHashIncreaseNumBuckets(hashtable);
/* The caller must retry. */ return NULL;
}
/* Oversized tuples get their own chunk. */ if (size > HASH_CHUNK_THRESHOLD)
chunk_size = size + HASH_CHUNK_HEADER_SIZE; else
chunk_size = HASH_CHUNK_SIZE;
/* Check if it's time to grow batches or buckets. */ if (pstate->growth != PHJ_GROWTH_DISABLED)
{
Assert(curbatch == 0);
Assert(BarrierPhase(&pstate->build_barrier) == PHJ_BUILD_HASH_INNER);
/* Check if our load factor limit would be exceeded. */ if (hashtable->nbatch == 1)
{
hashtable->batches[0].shared->ntuples += hashtable->batches[0].ntuples;
hashtable->batches[0].ntuples = 0; /* Guard against integer overflow and alloc size overflow */ if (hashtable->batches[0].shared->ntuples + 1 >
hashtable->nbuckets * NTUP_PER_BUCKET &&
hashtable->nbuckets < (INT_MAX / 2) &&
hashtable->nbuckets * 2 <=
MaxAllocSize / sizeof(dsa_pointer_atomic))
{
pstate->growth = PHJ_GROWTH_NEED_MORE_BUCKETS;
LWLockRelease(&pstate->lock);
return NULL;
}
}
}
/* We are cleared to allocate a new chunk. */
chunk_shared = dsa_allocate(hashtable->area, chunk_size);
hashtable->batches[curbatch].shared->size += chunk_size;
hashtable->batches[curbatch].at_least_one_chunk = true;
/* Set up the chunk. */
chunk = (HashMemoryChunk) dsa_get_address(hashtable->area, chunk_shared);
*shared = chunk_shared + HASH_CHUNK_HEADER_SIZE;
chunk->maxlen = chunk_size - HASH_CHUNK_HEADER_SIZE;
chunk->used = size;
/* Set up the shared state, tuplestores and backend-local accessors. */ for (i = 0; i < hashtable->nbatch; ++i)
{
ParallelHashJoinBatchAccessor *accessor = &hashtable->batches[i];
ParallelHashJoinBatch *shared = NthParallelHashJoinBatch(batches, i); char name[MAXPGPATH];
/* *Allmembersofsharedwerezero-initialized.Wejustneedtoset *uptheBarrier.
*/
BarrierInit(&shared->batch_barrier, 0); if (i == 0)
{ /* Batch 0 doesn't need to be loaded. */
BarrierAttach(&shared->batch_barrier); while (BarrierPhase(&shared->batch_barrier) < PHJ_BATCH_PROBE)
BarrierArriveAndWait(&shared->batch_barrier, 0);
BarrierDetach(&shared->batch_barrier);
}
/* Initialize accessor state. All members were zero-initialized. */
accessor->shared = shared;
/* *FreethecurrentsetofParallelHashJoinBatchAccessorobjects.
*/ staticvoid
ExecParallelHashCloseBatchAccessors(HashJoinTable hashtable)
{ int i;
for (i = 0; i < hashtable->nbatch; ++i)
{ /* Make sure no files are left open. */
sts_end_write(hashtable->batches[i].inner_tuples);
sts_end_write(hashtable->batches[i].outer_tuples);
sts_end_parallel_scan(hashtable->batches[i].inner_tuples);
sts_end_parallel_scan(hashtable->batches[i].outer_tuples);
}
pfree(hashtable->batches);
hashtable->batches = NULL;
}
/* Find the base of the pseudo-array of ParallelHashJoinBatch objects. */
batches = (ParallelHashJoinBatch *)
dsa_get_address(hashtable->area, pstate->batches);
/* Set up the accessor array and attach to the tuplestores. */ for (i = 0; i < hashtable->nbatch; ++i)
{
ParallelHashJoinBatchAccessor *accessor = &hashtable->batches[i];
ParallelHashJoinBatch *shared = NthParallelHashJoinBatch(batches, i);
/* Make sure any temporary files are closed. */
sts_end_parallel_scan(hashtable->batches[curbatch].inner_tuples);
sts_end_parallel_scan(hashtable->batches[curbatch].outer_tuples);
/* After attaching we always get at least to PHJ_BATCH_PROBE. */
Assert(BarrierPhase(&batch->batch_barrier) == PHJ_BATCH_PROBE ||
BarrierPhase(&batch->batch_barrier) == PHJ_BATCH_SCAN);
if (pstate && BarrierPhase(&pstate->build_barrier) == PHJ_BUILD_RUN)
{ int i;
/* Make sure any temporary files are closed. */ if (hashtable->batches)
{ for (i = 0; i < hashtable->nbatch; ++i)
{
sts_end_write(hashtable->batches[i].inner_tuples);
sts_end_write(hashtable->batches[i].outer_tuples);
sts_end_parallel_scan(hashtable->batches[i].inner_tuples);
sts_end_parallel_scan(hashtable->batches[i].outer_tuples);
}
}
/* If we're last to detach, clean up shared memory. */ if (BarrierArriveAndDetach(&pstate->build_barrier))
{ /* *Latejoiningprocesseswillseethisstateandgiveup *immediately.
*/
Assert(BarrierPhase(&pstate->build_barrier) == PHJ_BUILD_FREE);
/* Has another participant commanded us to help grow? */ if (pstate->growth == PHJ_GROWTH_NEED_MORE_BATCHES ||
pstate->growth == PHJ_GROWTH_NEED_MORE_BUCKETS)
{
ParallelHashGrowth growth = pstate->growth;
/* Do initial calculation in double arithmetic */
mem_limit = (double) work_mem * hash_mem_multiplier * 1024.0;
/* Clamp in case it doesn't fit in size_t */
mem_limit = Min(mem_limit, (double) SIZE_MAX);
return (size_t) mem_limit;
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.214Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.