Index: src/backend/executor/nodeHash.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/executor/nodeHash.c,v retrieving revision 1.116 diff -c -r1.116 nodeHash.c *** src/backend/executor/nodeHash.c 1 Jan 2008 19:45:49 -0000 1.116 --- src/backend/executor/nodeHash.c 29 Dec 2008 07:38:52 -0000 *************** *** 53,58 **** --- 53,201 ---- return NULL; } + /* + * ExecHashGetMCVPartition + * + * returns MCV_INVALID_PARTITION if the hashvalue does not correspond + * to any MCV partition or it corresponds to a partition that has been frozen + * or MCVs are not being used + * + * otherwise it returns the index of the MCV partition for this hashvalue + * + * it is possible for a non-MCV tuple to hash to an MCV partition due to + * the limited number of hash values but it is unlikely and everything + * continues to work even if it does happen. we would accidentally prioritize + * some less optimal tuples in memory but the result would be accurate + * + * hashtable->mostCommonTuplePartition is an open addressing hashtable of + * MCV partitions (HashJoinMostCommonValueTuplePartition) + */ + int + ExecHashGetMCVPartition(HashJoinTable hashtable, uint32 hashvalue) + { + int bucket; + + if (!hashtable->usingMostCommonValues) + return MCV_INVALID_PARTITION; + + /* modulo the hashvalue (using bitmask) to find the MCV partition */ + bucket = hashvalue & (hashtable->nMostCommonTuplePartitionHashBuckets - 1); + + /* + * while we have not hit a hole in the hashtable and have not hit the actual partition + * we have collided in the hashtable so try the next partition slot + */ + while (hashtable->mostCommonTuplePartition[bucket].hashvalue != 0 + && hashtable->mostCommonTuplePartition[bucket].hashvalue != hashvalue) + bucket = (bucket + 1) & (hashtable->nMostCommonTuplePartitionHashBuckets - 1); + + /* if the partition is not frozen and has been correctly determined return the partition index */ + if (!hashtable->mostCommonTuplePartition[bucket].frozen && hashtable->mostCommonTuplePartition[bucket].hashvalue == hashvalue) + return bucket; + + /* must have run into an empty slot which means this is not an MCV*/ + return MCV_INVALID_PARTITION; + } + + /* + * ExecHashFreezeNextMCVPartition + * + * flush the tuples of the next MCV partition by pushing them into the main hashtable + */ + static bool + ExecHashFreezeNextMCVPartition(HashJoinTable hashtable) { + /* + * calculate the flushOrderedMostCommonTuplePartition index of + * the partition to flush. not to be confused with the index of + * the partition in the MCV partitions hashtable + */ + int partitionToFlush = hashtable->nMostCommonTuplePartitions + - 1 - hashtable->nMostCommonTuplePartitionsFlushed; + int bucketno; + int batchno; + uint32 hashvalue; + HashJoinTuple hashTuple; + HashJoinTuple nextHashTuple; + HashJoinMostCommonValueTuplePartition *partition; + MinimalTuple mintuple; + + /* if all MCV partitions have already been flushed */ + if (partitionToFlush < 0) + return false; + + /* grab a pointer to the actual MCV partition */ + partition = hashtable->flushOrderedMostCommonTuplePartition[partitionToFlush]; + hashvalue = partition->hashvalue; + + Assert(hashvalue != 0); + + /* grab a pointer to the first tuple in the soon to be frozen MCV partition */ + hashTuple = partition->tuples; + + /* + * calculate which bucket and batch the tuples belong to in the main + * non-MCV hashtable + */ + ExecHashGetBucketAndBatch(hashtable, hashvalue, + &bucketno, &batchno); + + /* until we have read all tuples from this partition */ + while (hashTuple != NULL) + { + /* decide whether to put the tuple in the hash table or a temp file */ + if (batchno == hashtable->curbatch) + { + /* put the tuple in hash table */ + nextHashTuple = hashTuple->next; + hashTuple->next = hashtable->buckets[bucketno]; + hashtable->buckets[bucketno] = hashTuple; + + hashTuple = nextHashTuple; + hashtable->totalTuples++; + hashtable->nMostCommonTuplesStored--; + + /* + * since the tuple is not being removed from memory we may still + * be using too much memory. if this is the first tuple flushed + * then we definately must try to free some memory but we must test + * again for subsequent tuples in case more memory must be freed. + */ + if (hashtable->spaceUsed > hashtable->spaceAllowed) + { + ExecHashIncreaseNumBatches(hashtable); + /* batchno may have changed due to increase in batches */ + ExecHashGetBucketAndBatch(hashtable, hashvalue, + &bucketno, &batchno); + } + } + else + { + /* put the tuples into a temp file for later batches */ + Assert(batchno > hashtable->curbatch); + mintuple = HJTUPLE_MINTUPLE(hashTuple); + ExecHashJoinSaveTuple(mintuple, + hashvalue, + &hashtable->innerBatchFile[batchno]); + /* + * some memory has been freed up. this must be done before we + * pfree the hashTuple of we lose access to the tuple size + */ + hashtable->spaceUsed -= HJTUPLE_OVERHEAD + mintuple->t_len; + nextHashTuple = hashTuple->next; + pfree(hashTuple); + hashTuple = nextHashTuple; + hashtable->totalTuples++; + hashtable->nMostCommonTuplesStored--; + } + } + + partition->frozen = true; + partition->tuples = NULL; + hashtable->nMostCommonTuplePartitionsFlushed++; + + return true; + } + /* ---------------------------------------------------------------- * MultiExecHash * *************** *** 69,74 **** --- 212,219 ---- TupleTableSlot *slot; ExprContext *econtext; uint32 hashvalue; + MinimalTuple mintuple; + int partitionNumber; /* must provide our own instrumentation support */ if (node->ps.instrument) *************** *** 99,106 **** if (ExecHashGetHashValue(hashtable, econtext, hashkeys, false, false, &hashvalue)) { ! ExecHashTableInsert(hashtable, slot, hashvalue); ! hashtable->totalTuples += 1; } } --- 244,279 ---- if (ExecHashGetHashValue(hashtable, econtext, hashkeys, false, false, &hashvalue)) { ! partitionNumber = ExecHashGetMCVPartition(hashtable, hashvalue); ! ! /* if this tuple belongs in an MCV partition */ ! if (partitionNumber != MCV_INVALID_PARTITION) ! { ! HashJoinTuple hashTuple; ! int hashTupleSize; ! ! /* get the HashJoinTuple */ ! mintuple = ExecFetchSlotMinimalTuple(slot); ! hashTupleSize = HJTUPLE_OVERHEAD + mintuple->t_len; ! hashTuple = (HashJoinTuple) palloc(hashTupleSize); ! hashTuple->hashvalue = hashvalue; ! memcpy(HJTUPLE_MINTUPLE(hashTuple), mintuple, mintuple->t_len); ! ! /* push the HashJoinTuple onto the front of the MCV partition tuple list */ ! hashTuple->next = hashtable->mostCommonTuplePartition[partitionNumber].tuples; ! hashtable->mostCommonTuplePartition[partitionNumber].tuples = hashTuple; ! ! /* move memory is now in use so make sure we are not over memory */ ! hashtable->spaceUsed += hashTupleSize; ! hashtable->nMostCommonTuplesStored++; ! while (hashtable->spaceUsed > hashtable->spaceAllowed && ExecHashFreezeNextMCVPartition(hashtable)) ! ; ! } ! else ! { ! ExecHashTableInsert(hashtable, slot, hashvalue); ! hashtable->totalTuples += 1; ! } } } *************** *** 269,274 **** --- 442,454 ---- hashtable->outerBatchFile = NULL; hashtable->spaceUsed = 0; hashtable->spaceAllowed = work_mem * 1024L; + /* initialize MCV related hashtable variables */ + hashtable->usingMostCommonValues = false; + hashtable->nMostCommonTuplePartitions = 0; + hashtable->nMostCommonTuplePartitionHashBuckets = 0; + hashtable->nMostCommonTuplesStored = 0; + hashtable->mostCommonTuplePartition = NULL; + hashtable->nMostCommonTuplePartitionsFlushed = 0; /* * Get info about the hash functions to be used for each hash key. Also *************** *** 566,571 **** --- 746,752 ---- ExecHashJoinSaveTuple(HJTUPLE_MINTUPLE(tuple), tuple->hashvalue, &hashtable->innerBatchFile[batchno]); + /* and remove from hash table */ if (prevtuple) prevtuple->next = nexttuple; *************** *** 798,803 **** --- 979,1046 ---- } /* + * ExecScanHashMostCommonTuples + * scan a MCV partition for matches to the current outer tuple + * + * The current outer tuple must be stored in econtext->ecxt_outertuple. + */ + HashJoinTuple + ExecScanHashMostCommonTuples(HashJoinState *hjstate, + ExprContext *econtext) + { + List *hjclauses = hjstate->hashclauses; + HashJoinTable hashtable = hjstate->hj_HashTable; + HashJoinTuple hashTuple = hjstate->hj_CurTuple; + uint32 hashvalue = hjstate->hj_CurHashValue; + + /* + * hj_CurTuple is NULL to start scanning a new partition, or the address of + * the last tuple returned from the current partition. + */ + if (hashTuple == NULL) + { + /* painstakingly make sure this is a valid partition index */ + Assert(hjstate->hj_OuterTupleMostCommonValuePartition > MCV_INVALID_PARTITION); + Assert(hjstate->hj_OuterTupleMostCommonValuePartition < hashtable->nMostCommonTuplePartitionHashBuckets); + Assert(hashtable->mostCommonTuplePartition[hjstate->hj_OuterTupleMostCommonValuePartition].hashvalue != 0); + + hashTuple = hashtable->mostCommonTuplePartition[hjstate->hj_OuterTupleMostCommonValuePartition].tuples; + } + else + hashTuple = hashTuple->next; + + 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; + + /* reset temp memory each time to avoid leaks from qual expr */ + ResetExprContext(econtext); + + if (ExecQual(hjclauses, econtext, false)) + { + hjstate->hj_CurTuple = hashTuple; + return hashTuple; + } + } + + hashTuple = hashTuple->next; + } + + /* + * no match + */ + return NULL; + } + + /* * ExecScanHashBucket * scan a hash bucket for matches to the current outer tuple * Index: src/backend/executor/nodeHashjoin.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v retrieving revision 1.96 diff -c -r1.96 nodeHashjoin.c *** src/backend/executor/nodeHashjoin.c 23 Oct 2008 14:34:34 -0000 1.96 --- src/backend/executor/nodeHashjoin.c 29 Dec 2008 04:34:46 -0000 *************** *** 20,25 **** --- 20,30 ---- #include "executor/nodeHash.h" #include "executor/nodeHashjoin.h" #include "utils/memutils.h" + #include "optimizer/cost.h" + #include "utils/syscache.h" + #include "utils/lsyscache.h" + #include "parser/parsetree.h" + #include "catalog/pg_statistic.h" /* Returns true for JOIN_LEFT and JOIN_ANTI jointypes */ *************** *** 34,39 **** --- 39,159 ---- TupleTableSlot *tupleSlot); static int ExecHashJoinNewBatch(HashJoinState *hjstate); + /* + * ExecHashJoinGetMostCommonValues + * + * if the MCV statistics can be found for the join attribute of this + * hashjoin + * + */ + static void + ExecHashJoinGetMostCommonValues(EState *estate, HashJoinState *hjstate) + { + HeapTupleData *statsTuple; + FuncExprState *clause; + ExprState *argstate; + Var *variable; + + Datum *values; + int nvalues; + float4 *numbers; + int nnumbers; + + Oid relid; + Oid atttype; + + int i; + + /* Only use statistics if there is a single join attribute. */ + if (hjstate->hashclauses->length != 1) + return; /* histojoin is not defined for more than one join key so run away */ + + /* Determine the relation id and attribute id of the single join attribute of the probe relation. */ + + clause = (FuncExprState *) lfirst(list_head(hjstate->hashclauses)); + argstate = (ExprState *) lfirst(list_head(clause->args)); + + /* Do not try to exploit stats if the join attribute is an expression instead of just a simple attribute. */ + if (argstate->expr->type != T_Var) + return; + + variable = (Var *) argstate->expr; + relid = getrelid(variable->varnoold, estate->es_range_table); + + /* grab the necessary properties of the join variable */ + atttype = variable->vartype; + + statsTuple = SearchSysCache(STATRELATT, ObjectIdGetDatum(relid), + Int16GetDatum(variable->varoattno), 0, 0); + + if (!HeapTupleIsValid(statsTuple)) + return; + + /* if there are MCV statistics for the attribute */ + if (get_attstatsslot(statsTuple, + atttype, variable->vartypmod, + STATISTIC_KIND_MCV, InvalidOid, + &values, &nvalues, + &numbers, &nnumbers)) + { + MemoryContext oldcxt; + HashJoinTable hashtable; + FmgrInfo *hashfunctions; + /* MCV Partitions is an open addressing hashtable with a + power of 2 size greater than the number of MCV values. */ + int nbuckets = 2; + + while (nbuckets <= nvalues) + nbuckets <<= 1; + /* use two more bits just to help avoid collisions */ + nbuckets <<= 2; + + hashtable = hjstate->hj_HashTable; + hashtable->usingMostCommonValues = true; + hashtable->nMostCommonTuplePartitionHashBuckets = nbuckets; + + /* allocate the partition memory in the hashtable's memory context */ + oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); + hashtable->mostCommonTuplePartition = palloc0(nbuckets * sizeof(HashJoinMostCommonValueTuplePartition)); + hashtable->flushOrderedMostCommonTuplePartition = palloc0(nvalues * sizeof(HashJoinMostCommonValueTuplePartition*)); + MemoryContextSwitchTo(oldcxt); + + /* grab the hash functions as we will be generating the hashvalues here */ + hashfunctions = hashtable->outer_hashfunctions; + + /* create the partitions */ + for (i = 0; i < nvalues; i++) + { + uint32 hashvalue = DatumGetUInt32(FunctionCall1(&hashfunctions[0], values[i])); + int bucket = hashvalue & (nbuckets - 1); + + /* + * while we have not hit a hole in the hashtable and have not hit a partition + * with the same hashvalue we have collided in the hashtable so try the next + * partition slot (remember it is an open addressing hashtable) + */ + while (hashtable->mostCommonTuplePartition[bucket].hashvalue != 0 + && hashtable->mostCommonTuplePartition[bucket].hashvalue != hashvalue) + bucket = (bucket + 1) & (nbuckets - 1); + + /* + * leave partition alone if it has the same hashvalue as current MCV. + * we only want one partition per hashvalue. even if two MCV values + * hash to the same partition we are fine + */ + if (hashtable->mostCommonTuplePartition[bucket].hashvalue != hashvalue) + { + hashtable->mostCommonTuplePartition[bucket].hashvalue = hashvalue; + hashtable->flushOrderedMostCommonTuplePartition[hashtable->nMostCommonTuplePartitions] = &hashtable->mostCommonTuplePartition[bucket]; + hashtable->nMostCommonTuplePartitions++; + } + } + + free_attstatsslot(atttype, values, nvalues, numbers, nnumbers); + } + + ReleaseSysCache(statsTuple); + } /* ---------------------------------------------------------------- * ExecHashJoin *************** *** 147,152 **** --- 267,276 ---- node->hj_HashOperators); node->hj_HashTable = hashtable; + /* we dont bother exploiting MCVs if we can do the entire join in memory */ + if (hashtable->nbatch > 1) + ExecHashJoinGetMostCommonValues(estate, node); + /* * execute the Hash node, to build the hash table */ *************** *** 157,163 **** * If the inner relation is completely empty, and we're not doing an * outer join, we can quit without scanning the outer relation. */ ! if (hashtable->totalTuples == 0 && !HASHJOIN_IS_OUTER(node)) return NULL; /* --- 281,287 ---- * If the inner relation is completely empty, and we're not doing an * outer join, we can quit without scanning the outer relation. */ ! if (hashtable->totalTuples == 0 && hashtable->nMostCommonTuplesStored == 0 && !HASHJOIN_IS_OUTER(node)) return NULL; /* *************** *** 205,227 **** ExecHashGetBucketAndBatch(hashtable, hashvalue, &node->hj_CurBucketNo, &batchno); node->hj_CurTuple = NULL; ! /* ! * Now we've got an outer tuple and the corresponding hash bucket, ! * but this tuple may not belong to the current batch. ! */ ! if (batchno != hashtable->curbatch) { /* ! * Need to postpone this outer tuple to a later batch. Save it ! * in the corresponding outer-batch file. */ ! Assert(batchno > hashtable->curbatch); ! ExecHashJoinSaveTuple(ExecFetchSlotMinimalTuple(outerTupleSlot), ! hashvalue, ! &hashtable->outerBatchFile[batchno]); ! node->hj_NeedNewOuter = true; ! continue; /* loop around for a new outer tuple */ } } --- 329,356 ---- ExecHashGetBucketAndBatch(hashtable, hashvalue, &node->hj_CurBucketNo, &batchno); node->hj_CurTuple = NULL; + + node->hj_OuterTupleMostCommonValuePartition = ExecHashGetMCVPartition(hashtable, hashvalue); ! if (node->hj_OuterTupleMostCommonValuePartition == MCV_INVALID_PARTITION) { /* ! * Now we've got an outer tuple and the corresponding hash bucket, ! * but this tuple may not belong to the current batch. */ ! if (batchno != hashtable->curbatch) ! { ! /* ! * Need to postpone this outer tuple to a later batch. Save it ! * in the corresponding outer-batch file. ! */ ! Assert(batchno > hashtable->curbatch); ! ExecHashJoinSaveTuple(ExecFetchSlotMinimalTuple(outerTupleSlot), ! hashvalue, ! &hashtable->outerBatchFile[batchno]); ! node->hj_NeedNewOuter = true; ! continue; /* loop around for a new outer tuple */ ! } } } *************** *** 230,236 **** */ for (;;) { ! curtuple = ExecScanHashBucket(node, econtext); if (curtuple == NULL) break; /* out of matches */ --- 359,373 ---- */ for (;;) { ! /* if the tuple hashed to an MCV partition then scan the MCV tuples */ ! if (node->hj_OuterTupleMostCommonValuePartition != MCV_INVALID_PARTITION) ! { ! curtuple = ExecScanHashMostCommonTuples(node, econtext); ! } ! else /* otherwise scan the standard hashtable buckets */ ! { ! curtuple = ExecScanHashBucket(node, econtext); ! } if (curtuple == NULL) break; /* out of matches */ Index: src/include/executor/hashjoin.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/executor/hashjoin.h,v retrieving revision 1.48 diff -c -r1.48 hashjoin.h *** src/include/executor/hashjoin.h 1 Jan 2008 19:45:57 -0000 1.48 --- src/include/executor/hashjoin.h 29 Dec 2008 01:39:50 -0000 *************** *** 72,77 **** --- 72,85 ---- #define HJTUPLE_MINTUPLE(hjtup) \ ((MinimalTuple) ((char *) (hjtup) + HJTUPLE_OVERHEAD)) + typedef struct HashJoinMostCommonValueTuplePartition + { + uint32 hashvalue; + bool frozen; + HashJoinTuple tuples; + } HashJoinMostCommonValueTuplePartition; + + #define MCV_INVALID_PARTITION -1 typedef struct HashJoinTableData { *************** *** 116,121 **** --- 124,141 ---- MemoryContext hashCxt; /* context for whole-hash-join storage */ MemoryContext batchCxt; /* context for this-batch-only storage */ + + bool usingMostCommonValues; /* will the join use MCV partitions */ + HashJoinMostCommonValueTuplePartition *mostCommonTuplePartition; /* hashtable of MCV partitions */ + /* + * array of pointers to the MCV partitions hashtable buckets in the opposite order that + * they would be flushed to disk + */ + HashJoinMostCommonValueTuplePartition **flushOrderedMostCommonTuplePartition; + int nMostCommonTuplePartitionHashBuckets; /* # of buckets in the MCV partitions hashtable */ + int nMostCommonTuplePartitions; /* # of actual partitions in the MCV partitions hashtable */ + int nMostCommonTuplePartitionsFlushed; /* # of MCV partitions that have already been flushed to disk */ + uint32 nMostCommonTuplesStored; /* total # of build tuples currently stored in the MCV partitions */ } HashJoinTableData; #endif /* HASHJOIN_H */ Index: src/include/executor/nodeHash.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/executor/nodeHash.h,v retrieving revision 1.45 diff -c -r1.45 nodeHash.h *** src/include/executor/nodeHash.h 1 Jan 2008 19:45:57 -0000 1.45 --- src/include/executor/nodeHash.h 23 Dec 2008 07:54:03 -0000 *************** *** 44,48 **** --- 44,51 ---- extern void ExecChooseHashTableSize(double ntuples, int tupwidth, int *numbuckets, int *numbatches); + + extern HashJoinTuple ExecScanHashMostCommonTuples(HashJoinState *hjstate, ExprContext *econtext); + extern int ExecHashGetMCVPartition(HashJoinTable hashtable, uint32 hashvalue); #endif /* NODEHASH_H */ Index: src/include/nodes/execnodes.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/nodes/execnodes.h,v retrieving revision 1.197 diff -c -r1.197 execnodes.h *** src/include/nodes/execnodes.h 28 Dec 2008 18:54:00 -0000 1.197 --- src/include/nodes/execnodes.h 29 Dec 2008 04:40:02 -0000 *************** *** 1381,1386 **** --- 1381,1387 ---- * hj_NeedNewOuter true if need new outer tuple on next call * hj_MatchedOuter true if found a join match for current outer * hj_OuterNotEmpty true if outer relation known not empty + * hj_OuterTupleMostCommonValuePartition partition# for the current outer tuple * ---------------- */ *************** *** 1406,1411 **** --- 1407,1413 ---- bool hj_NeedNewOuter; bool hj_MatchedOuter; bool hj_OuterNotEmpty; + int hj_OuterTupleMostCommonValuePartition; } HashJoinState;