From 787dacf4f95b8a07266b6545b45e3b6d29d5abec Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Tue, 10 Jun 2025 17:23:06 +1000 Subject: [PATCH v20250610] VCI module - VCI_ENABLE_HOT_HASH_TABLE code --- contrib/vci/executor/vci_agg.c | 51 +++++++ contrib/vci/executor/vci_hothash.c | 286 +++++++++++++++++++++++++++++++++++++ contrib/vci/include/vci.h | 3 + contrib/vci/include/vci_executor.h | 38 +++++ contrib/vci/include/vci_mem.h | 3 + contrib/vci/vci_read_guc.c | 14 ++ 6 files changed, 395 insertions(+) create mode 100644 contrib/vci/executor/vci_hothash.c diff --git a/contrib/vci/executor/vci_agg.c b/contrib/vci/executor/vci_agg.c index af7df05..d486c11 100644 --- a/contrib/vci/executor/vci_agg.c +++ b/contrib/vci/executor/vci_agg.c @@ -466,6 +466,12 @@ build_hash_table(VciAggState *aggstate) aggstate->aggcontext, tmpmem, false); +#ifdef VCI_ENABLE_HOT_HASH_TABLE + if (VciCanUseHotHashTable(aggstate)) + aggstate->hottable = VciBuildTupleHotHashTable(additionsize + sizeof(TupleHashEntryData), + aggstate->aggcontext); +#endif /* VCI_ENABLE_HOT_HASH_TABLE */ + } /** @@ -594,6 +600,24 @@ lookup_hash_entry_vector(VciAggState *aggstate, } ExecStoreVirtualTuple(hashslot); + +#ifdef VCI_ENABLE_HOT_HASH_TABLE + if (aggstate->hottable) + { + uint64 key = VciGetHotHashKey(aggstate, hashslot); + + if (key < aggstate->hottable->numEntries) + { + entry = VciLookupTupleHotHashEntry(aggstate->hottable, + key, + hashslot, + &isnew); + hashcxt = aggstate->hottable->tablecxt; + goto found_hot_table; + } + } +#endif + /* find or create the hashtable entry using the filtered tuple */ entry = LookupTupleHashEntry(aggstate->hashtable, hashslot, @@ -601,6 +625,10 @@ lookup_hash_entry_vector(VciAggState *aggstate, NULL); hashcxt = aggstate->hashtable->tablecxt; +#ifdef VCI_ENABLE_HOT_HASH_TABLE +found_hot_table: +#endif + if (isnew && aggstate->numaggs) { entry->additional = (VciAggStatePerGroup) @@ -983,6 +1011,25 @@ vci_agg_retrieve_hash_table(VciAggState *aggstate) TupleHashEntry vci_agg_find_group_from_hash_table(VciAggState *aggstate) { +#ifdef VCI_ENABLE_HOT_HASH_TABLE + VciTupleHotHashTable hottable = aggstate->hottable; + + if (aggstate->hottable) + { + while (hottable->iterator < hottable->numEntries) + { + TupleHashEntry entry; + + entry = (TupleHashEntry) hottable->entries[hottable->iterator++]; + + if (entry == NULL) + continue; + + return entry; + } + } +#endif + while (!aggstate->agg_done) { return (TupleHashEntry) ScanTupleHashTable(aggstate->hashtable, &aggstate->hashiter); @@ -1798,6 +1845,10 @@ vci_agg_ReScanCustomPlan(CustomScanState *node) if (aggstate->vci.css.ss.ps.lefttree->chgParam == NULL) { ResetTupleHashIterator(aggstate->hashtable, &aggstate->hashiter); +#ifdef VCI_ENABLE_HOT_HASH_TABLE + if (aggstate->hottable) + aggstate->hottable->iterator = 0; +#endif return; } } diff --git a/contrib/vci/executor/vci_hothash.c b/contrib/vci/executor/vci_hothash.c new file mode 100644 index 0000000..04a3860 --- /dev/null +++ b/contrib/vci/executor/vci_hothash.c @@ -0,0 +1,286 @@ +/*------------------------------------------------------------------------- + * + * vci_hothash.c + * When there is no has collision in hash table and hash keys are small, + * performance can be improved by preparing direct map table before the + * original hash table. + * + * Portions Copyright (c) 2025, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/vci/executor/vci_hothash.c + */ +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/tuptable.h" +#include "fmgr.h" +#include "nodes/execnodes.h" +#include "utils/palloc.h" + +#include "vci.h" +#include "vci_mem.h" + +#include "vci_executor.h" + +#ifdef VCI_ENABLE_HOT_HASH_TABLE + +/** + * Determine if Hot Hashtable is usable + * + * @param[in] aggstate Pointer to VCI Agg State + * @return Return true if usable, false if not + * + * Usable datatypes are + * - Basic integer type + * - 1 character only char + */ +bool +VciCanUseHotHashTable(VciAggState *aggstate) +{ + VciScanState *scanstate; + TupleTableSlot *pseudo_inputslot; + TupleDesc tupleDesc; + int i; + uint64 shift = 0; + + scanstate = (VciScanState *) outerPlanState(aggstate); + pseudo_inputslot = scanstate->vci.css.ss.ps.ps_ResultTupleSlot; + tupleDesc = pseudo_inputslot->tts_tupleDescriptor; + + for (i = 0; i < aggstate->num_hash_needed; i++) + { + int varNumber; + Form_pg_attribute attr; + + varNumber = aggstate->hash_needed[i] - 1; + attr = TupleDescAttr(tupleDesc, varNumber); + + if (VciGuc.hot_hashtable_bits < shift) + return false; + + switch (attr->atttypid) + { + case BOOLOID: + shift += 1; + break; + + case CHAROID: + shift += 8; + break; + + case INT2OID: + shift += 16; + break; + + case INT4OID: + shift += 32; + break; + + case INT8OID: + shift += 64; + break; + + case BPCHAROID: + if (attr->atttypmod != VARHDRSZ + 1) + return false; + shift += 8; + break; + + default: + return false; + } + } + + return true; +} + +/** + * Create and return new Hot Hashtable + * + * @param[in] entrysize Entry size(byte) + * @param[in] tablecxt memory context for allocating data in Hot hashtable + * @return Pointer to Hot hashtable + */ +VciTupleHotHashTable +VciBuildTupleHotHashTable(Size entrysize, MemoryContext tablecxt) +{ + VciTupleHotHashTable hottable; + + Assert(entrysize >= sizeof(TupleHashEntryData)); + + hottable = (VciTupleHotHashTable) MemoryContextAlloc(tablecxt, + sizeof(VciTupleHotHashTableData)); + + hottable->tablecxt = tablecxt; + hottable->entrysize = entrysize; + hottable->tableslot = NULL; /* will be made on first lookup */ + hottable->inputslot = NULL; + + hottable->numEntries = 1U << (VciGuc.hot_hashtable_bits - 1); + hottable->entries = palloc0(sizeof(TupleHashEntry) * hottable->numEntries); + + hottable->iterator = 0; + + return hottable; +} + +/** + * Return entry corresponds to key from hot hashtable. + * If the entry doesn't exists, it is created and then returned. + * + * @param[in,out] hottable Pointer to Hot hastable + * @param[in] key hash key + * @param[in] slot tuple to be stored in hash + * @param[out] isnew_p Return true if key is a new entry in hot hashtable, + * false if not + * @return Pointer to hash entry + * + * The tuple table slot given in slot is converted to minimal tuple, + * then copied and stored in internal memory context of hottable. + */ +TupleHashEntry +VciLookupTupleHotHashEntry(VciTupleHotHashTable hottable, uint64 key, + TupleTableSlot *slot, + bool *isnew_p) +{ + TupleHashEntry entry; + MemoryContext oldContext; + bool isnew = false; + + Assert(key < hottable->numEntries); + + /* If first time through, clone the input slot to make table slot */ + if (hottable->tableslot == NULL) + { + TupleDesc tupdesc; + + oldContext = MemoryContextSwitchTo(hottable->tablecxt); + + /* + * We copy the input tuple descriptor just for safety --- we assume + * all input tuples will have equivalent descriptors. + */ + tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor); + hottable->tableslot = MakeSingleTupleTableSlot(tupdesc, + &TTSOpsMinimalTuple); + MemoryContextSwitchTo(oldContext); + } + + entry = hottable->entries[key]; + + if (entry == NULL) + { + oldContext = MemoryContextSwitchTo(hottable->tablecxt); + + entry = palloc0(sizeof(TupleHashEntryData)); + /* Copy the first tuple into the table context */ + entry->firstTuple = ExecCopySlotMinimalTuple(slot); + MemoryContextSwitchTo(oldContext); + + hottable->entries[key] = entry; + + isnew = true; + } + + if (isnew_p) + *isnew_p = isnew; + + return entry; +} + +/** + * Returns hash key value for input hashslot + * + * @param[in] aggstate Pointer to VCI Agg State + * @param[in] hashslot Input tuple table slot + * @return hash key + * + * @note hashslot must be in Virtual Tuple format + */ +uint64 +VciGetHotHashKey(VciAggState *aggstate, TupleTableSlot *hashslot) +{ + TupleDesc tupleDesc; + int i; + uint64 key = 0, + shift = 0; + + tupleDesc = hashslot->tts_tupleDescriptor; + + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(hashslot); + + for (i = 0; i < aggstate->num_hash_needed; i++) + { + int varNumber = aggstate->hash_needed[i] - 1; + + Assert(varNumber < hashslot->tts_nvalid); + + if (hashslot->tts_isnull[varNumber]) + { + return UINT64_MAX; + } + else + { + Form_pg_attribute attr; + Datum value; + + attr = TupleDescAttr(tupleDesc, varNumber); + value = hashslot->tts_values[varNumber]; + + switch (attr->atttypid) + { + case BOOLOID: + key |= (DatumGetBool(value) ? 1 : 0) << shift; + shift += 1; + break; + + case CHAROID: + key |= ((unsigned char) DatumGetChar(value)) << shift; + shift += 8; + break; + + case INT2OID: + key |= ((uint16) DatumGetInt16(value)) << shift; + shift += 16; + break; + + case INT4OID: + key |= ((uint32) DatumGetInt32(value)) << shift; + shift += 32; + break; + + case INT8OID: + key |= ((uint64) DatumGetInt64(value)) << shift; + shift += 64; + break; + + case BPCHAROID: + { + BpChar *source = DatumGetBpCharPP(value); + char *s; + + if (1 < VARSIZE_ANY_EXHDR(source)) + return UINT64_MAX; + + s = VARDATA_ANY(source); + + key |= *((uint8 *) s) << shift; + shift += 8; + } + break; + + default: + /* LCOV_EXCL_START */ + elog(PANIC, "should not reach here"); + break; + /* LCOV_EXCL_STOP */ + } + } + } + + return key; +} + +#endif /* VCI_ENABLE_HOT_HASH_TABLE */ diff --git a/contrib/vci/include/vci.h b/contrib/vci/include/vci.h index 3e4426a..a0abdd5 100644 --- a/contrib/vci/include/vci.h +++ b/contrib/vci/include/vci.h @@ -37,6 +37,9 @@ #define VCI_INTERNAL_RELATION_TEMPLATE "vci_%010d_%05d_%c" +/** Enables Hot hashtable */ +#define VCI_ENABLE_HOT_HASH_TABLE + /** Use compact form to keep varlena with short header at some parts */ #define VCI_USE_COMPACT_VARLENA diff --git a/contrib/vci/include/vci_executor.h b/contrib/vci/include/vci_executor.h index eb40f74..390842a 100644 --- a/contrib/vci/include/vci_executor.h +++ b/contrib/vci/include/vci_executor.h @@ -321,6 +321,40 @@ typedef struct VciVPContext extern void VciExecEvalVectorProcessing(VciVPContext *vpcontext, ExprContext *econtext, int max_slots); extern VciVPContext *VciBuildVectorProcessing(Expr *node, PlanState *parent, ExprContext *econtext, uint16 *skip_list); + +#ifdef VCI_ENABLE_HOT_HASH_TABLE +/* ---------------- + * Hot hashtable + * ---------------- + */ + +/** + * Hot hash table data struct + */ +typedef struct VciTupleHotHashTableData +{ + int numEntries; /* Number of entry */ + TupleHashEntry *entries; /* Array of entry */ + + MemoryContext tablecxt; /* memory context containing table */ + Size entrysize; /* actual size to make each hash entry */ + TupleTableSlot *tableslot; /* slot for referencing table entries */ + /* The following fields are set transiently for each table search: */ + TupleTableSlot *inputslot; /* current input tuple's slot */ + + int iterator; /* Iterator used during Hot hast table scan */ +} VciTupleHotHashTableData; + +typedef struct VciTupleHotHashTableData *VciTupleHotHashTable; + +extern VciTupleHotHashTable VciBuildTupleHotHashTable(Size entrysize, MemoryContext tablecxt); +extern TupleHashEntry VciLookupTupleHotHashEntry(VciTupleHotHashTable hashtable, uint64 key, + TupleTableSlot *slot, bool *isnew_p); +extern bool VciCanUseHotHashTable(struct VciAggState *aggstate); +extern uint64 VciGetHotHashKey(struct VciAggState *aggstate, TupleTableSlot *hashslot); + +#endif /* VCI_ENABLE_HOT_HASH_TABLE */ + /* ---------------- * Projection information for VCI * ---------------- @@ -599,6 +633,10 @@ typedef struct VciAggState bool table_filled; /* hash table filled yet? */ TupleHashIterator hashiter; /* for iterating through hash table */ +#ifdef VCI_ENABLE_HOT_HASH_TABLE + VciTupleHotHashTable hottable; +#endif + /* * aggregation function changes its behaviour by checking AggState * Therefore, ExecEvalExpr() shows dummy AggState, not VciAggState diff --git a/contrib/vci/include/vci_mem.h b/contrib/vci/include/vci_mem.h index 93a814a..7f9d2cb 100644 --- a/contrib/vci/include/vci_mem.h +++ b/contrib/vci/include/vci_mem.h @@ -67,6 +67,9 @@ typedef struct VciGucStruct /* GUC parameters for internal use */ bool enable_ros_control_daemon; +#ifdef VCI_ENABLE_HOT_HASH_TABLE + int hot_hashtable_bits; +#endif } VciGucStruct; diff --git a/contrib/vci/vci_read_guc.c b/contrib/vci/vci_read_guc.c index 99914cb..33b4ab1 100644 --- a/contrib/vci/vci_read_guc.c +++ b/contrib/vci/vci_read_guc.c @@ -341,6 +341,20 @@ static struct config_int VciConfigureNamesInt[] = NULL, NULL, NULL, }, +#ifdef VCI_ENABLE_HOT_HASH_TABLE + { + { + "vci.hot_hashtable_bits", + PGC_USERSET, DEVELOPER_OPTIONS, + "Sets the number of bits specified by the size of hot hashtable entries.", + NULL, + }, + &VciGuc.hot_hashtable_bits, + 16, 8, 24, + NULL, NULL, NULL, + }, +#endif + }; static const struct config_enum_entry table_scan_policy_options[] = { -- 1.8.3.1