Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
simplehash.h1188 linesDownload Raw Back to lib
1/*2 * simplehash.h3 *4 *	  When included this file generates a "templated" (by way of macros)5 *	  open-addressing hash table implementation specialized to user-defined6 *	  types.7 *8 *	  It's probably not worthwhile to generate such a specialized implementation9 *	  for hash tables that aren't performance or space sensitive.10 *11 *	  Compared to dynahash, simplehash has the following benefits:12 *13 *	  - Due to the "templated" code generation has known structure sizes and no14 *	    indirect function calls (which show up substantially in dynahash15 *	    profiles). These features considerably increase speed for small16 *	    entries.17 *	  - Open addressing has better CPU cache behavior than dynahash's chained18 *	    hashtables.19 *	  - The generated interface is type-safe and easier to use than dynahash,20 *	    though at the cost of more complex setup.21 *	  - Allocates memory in a MemoryContext or another allocator with a22 *	    malloc/free style interface (which isn't easily usable in a shared23 *	    memory context)24 *	  - Does not require the overhead of a separate memory context.25 *26 * Usage notes:27 *28 *	  To generate a hash-table and associated functions for a use case several29 *	  macros have to be #define'ed before this file is included.  Including30 *	  the file #undef's all those, so a new hash table can be generated31 *	  afterwards.32 *	  The relevant parameters are:33 *	  - SH_PREFIX - prefix for all symbol names generated. A prefix of 'foo'34 *		will result in hash table type 'foo_hash' and functions like35 *		'foo_insert'/'foo_lookup' and so forth.36 *	  - SH_ELEMENT_TYPE - type of the contained elements37 *	  - SH_KEY_TYPE - type of the hashtable's key38 *	  - SH_DECLARE - if defined function prototypes and type declarations are39 *		generated40 *	  - SH_DEFINE - if defined function definitions are generated41 *	  - SH_SCOPE - in which scope (e.g. extern, static inline) do function42 *		declarations reside43 *	  - SH_RAW_ALLOCATOR - if defined, memory contexts are not used; instead,44 *	    use this to allocate bytes. The allocator must zero the returned space.45 *	  - SH_USE_NONDEFAULT_ALLOCATOR - if defined no element allocator functions46 *		are defined, so you can supply your own47 *	  The following parameters are only relevant when SH_DEFINE is defined:48 *	  - SH_KEY - name of the element in SH_ELEMENT_TYPE containing the hash key49 *	  - SH_EQUAL(table, a, b) - compare two table keys50 *	  - SH_HASH_KEY(table, key) - generate hash for the key51 *	  - SH_STORE_HASH - if defined the hash is stored in the elements52 *	  - SH_GET_HASH(tb, a) - return the field to store the hash in53 *54 *	  The element type is required to contain a "status" member that can store55 *	  the range of values defined in the SH_STATUS enum.56 *57 *	  While SH_STORE_HASH (and subsequently SH_GET_HASH) are optional, because58 *	  the hash table implementation needs to compare hashes to move elements59 *	  (particularly when growing the hash), it's preferable, if possible, to60 *	  store the element's hash in the element's data type. If the hash is so61 *	  stored, the hash table will also compare hashes before calling SH_EQUAL62 *	  when comparing two keys.63 *64 *	  For convenience the hash table create functions accept a void pointer65 *	  that will be stored in the hash table type's member private_data. This66 *	  allows callbacks to reference caller provided data.67 *68 *	  For examples of usage look at tidbitmap.c (file local definition) and69 *	  execnodes.h/execGrouping.c (exposed declaration, file local70 *	  implementation).71 *72 * Hash table design:73 *74 *	  The hash table design chosen is a variant of linear open-addressing. The75 *	  reason for doing so is that linear addressing is CPU cache & pipeline76 *	  friendly. The biggest disadvantage of simple linear addressing schemes77 *	  are highly variable lookup times due to clustering, and deletions78 *	  leaving a lot of tombstones around.  To address these issues a variant79 *	  of "robin hood" hashing is employed.  Robin hood hashing optimizes80 *	  chaining lengths by moving elements close to their optimal bucket81 *	  ("rich" elements), out of the way if a to-be-inserted element is further82 *	  away from its optimal position (i.e. it's "poor").  While that can make83 *	  insertions slower, the average lookup performance is a lot better, and84 *	  higher fill factors can be used in a still performant manner.  To avoid85 *	  tombstones - which normally solve the issue that a deleted node's86 *	  presence is relevant to determine whether a lookup needs to continue87 *	  looking or is done - buckets following a deleted element are shifted88 *	  backwards, unless they're empty or already at their optimal position.89 *90 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group91 * Portions Copyright (c) 1994, Regents of the University of California92 *93 * src/include/lib/simplehash.h94 */95 96#include "port/pg_bitutils.h"97 98/* helpers */99#define SH_MAKE_PREFIX(a) CppConcat(a,_)100#define SH_MAKE_NAME(name) SH_MAKE_NAME_(SH_MAKE_PREFIX(SH_PREFIX),name)101#define SH_MAKE_NAME_(a,b) CppConcat(a,b)102 103/* name macros for: */104 105/* type declarations */106#define SH_TYPE SH_MAKE_NAME(hash)107#define SH_STATUS SH_MAKE_NAME(status)108#define SH_STATUS_EMPTY SH_MAKE_NAME(SH_EMPTY)109#define SH_STATUS_IN_USE SH_MAKE_NAME(SH_IN_USE)110#define SH_ITERATOR SH_MAKE_NAME(iterator)111 112/* function declarations */113#define SH_CREATE SH_MAKE_NAME(create)114#define SH_DESTROY SH_MAKE_NAME(destroy)115#define SH_RESET SH_MAKE_NAME(reset)116#define SH_INSERT SH_MAKE_NAME(insert)117#define SH_INSERT_HASH SH_MAKE_NAME(insert_hash)118#define SH_DELETE_ITEM SH_MAKE_NAME(delete_item)119#define SH_DELETE SH_MAKE_NAME(delete)120#define SH_LOOKUP SH_MAKE_NAME(lookup)121#define SH_LOOKUP_HASH SH_MAKE_NAME(lookup_hash)122#define SH_GROW SH_MAKE_NAME(grow)123#define SH_START_ITERATE SH_MAKE_NAME(start_iterate)124#define SH_START_ITERATE_AT SH_MAKE_NAME(start_iterate_at)125#define SH_ITERATE SH_MAKE_NAME(iterate)126#define SH_ALLOCATE SH_MAKE_NAME(allocate)127#define SH_FREE SH_MAKE_NAME(free)128#define SH_STAT SH_MAKE_NAME(stat)129 130/* internal helper functions (no externally visible prototypes) */131#define SH_COMPUTE_PARAMETERS SH_MAKE_NAME(compute_parameters)132#define SH_NEXT SH_MAKE_NAME(next)133#define SH_PREV SH_MAKE_NAME(prev)134#define SH_DISTANCE_FROM_OPTIMAL SH_MAKE_NAME(distance)135#define SH_INITIAL_BUCKET SH_MAKE_NAME(initial_bucket)136#define SH_ENTRY_HASH SH_MAKE_NAME(entry_hash)137#define SH_INSERT_HASH_INTERNAL SH_MAKE_NAME(insert_hash_internal)138#define SH_LOOKUP_HASH_INTERNAL SH_MAKE_NAME(lookup_hash_internal)139 140/* generate forward declarations necessary to use the hash table */141#ifdef SH_DECLARE142 143/* type definitions */144typedef struct SH_TYPE145{146	/*147	 * Size of data / bucket array, 64 bits to handle UINT32_MAX sized hash148	 * tables.  Note that the maximum number of elements is lower149	 * (SH_MAX_FILLFACTOR)150	 */151	uint64		size;152 153	/* how many elements have valid contents */154	uint32		members;155 156	/* mask for bucket and size calculations, based on size */157	uint32		sizemask;158 159	/* boundary after which to grow hashtable */160	uint32		grow_threshold;161 162	/* hash buckets */163	SH_ELEMENT_TYPE *data;164 165#ifndef SH_RAW_ALLOCATOR166	/* memory context to use for allocations */167	MemoryContext ctx;168#endif169 170	/* user defined data, useful for callbacks */171	void	   *private_data;172}			SH_TYPE;173 174typedef enum SH_STATUS175{176	SH_STATUS_EMPTY = 0x00,177	SH_STATUS_IN_USE = 0x01178} SH_STATUS;179 180typedef struct SH_ITERATOR181{182	uint32		cur;			/* current element */183	uint32		end;184	bool		done;			/* iterator exhausted? */185}			SH_ITERATOR;186 187/* externally visible function prototypes */188#ifdef SH_RAW_ALLOCATOR189/* <prefix>_hash <prefix>_create(uint32 nelements, void *private_data) */190SH_SCOPE	SH_TYPE *SH_CREATE(uint32 nelements, void *private_data);191#else192/*193 * <prefix>_hash <prefix>_create(MemoryContext ctx, uint32 nelements,194 *								 void *private_data)195 */196SH_SCOPE	SH_TYPE *SH_CREATE(MemoryContext ctx, uint32 nelements,197							   void *private_data);198#endif199 200/* void <prefix>_destroy(<prefix>_hash *tb) */201SH_SCOPE void SH_DESTROY(SH_TYPE * tb);202 203/* void <prefix>_reset(<prefix>_hash *tb) */204SH_SCOPE void SH_RESET(SH_TYPE * tb);205 206/* void <prefix>_grow(<prefix>_hash *tb, uint64 newsize) */207SH_SCOPE void SH_GROW(SH_TYPE * tb, uint64 newsize);208 209/* <element> *<prefix>_insert(<prefix>_hash *tb, <key> key, bool *found) */210SH_SCOPE	SH_ELEMENT_TYPE *SH_INSERT(SH_TYPE * tb, SH_KEY_TYPE key, bool *found);211 212/*213 * <element> *<prefix>_insert_hash(<prefix>_hash *tb, <key> key, uint32 hash,214 * 								  bool *found)215 */216SH_SCOPE	SH_ELEMENT_TYPE *SH_INSERT_HASH(SH_TYPE * tb, SH_KEY_TYPE key,217											uint32 hash, bool *found);218 219/* <element> *<prefix>_lookup(<prefix>_hash *tb, <key> key) */220SH_SCOPE	SH_ELEMENT_TYPE *SH_LOOKUP(SH_TYPE * tb, SH_KEY_TYPE key);221 222/* <element> *<prefix>_lookup_hash(<prefix>_hash *tb, <key> key, uint32 hash) */223SH_SCOPE	SH_ELEMENT_TYPE *SH_LOOKUP_HASH(SH_TYPE * tb, SH_KEY_TYPE key,224											uint32 hash);225 226/* void <prefix>_delete_item(<prefix>_hash *tb, <element> *entry) */227SH_SCOPE void SH_DELETE_ITEM(SH_TYPE * tb, SH_ELEMENT_TYPE * entry);228 229/* bool <prefix>_delete(<prefix>_hash *tb, <key> key) */230SH_SCOPE bool SH_DELETE(SH_TYPE * tb, SH_KEY_TYPE key);231 232/* void <prefix>_start_iterate(<prefix>_hash *tb, <prefix>_iterator *iter) */233SH_SCOPE void SH_START_ITERATE(SH_TYPE * tb, SH_ITERATOR * iter);234 235/*236 * void <prefix>_start_iterate_at(<prefix>_hash *tb, <prefix>_iterator *iter,237 *								  uint32 at)238 */239SH_SCOPE void SH_START_ITERATE_AT(SH_TYPE * tb, SH_ITERATOR * iter, uint32 at);240 241/* <element> *<prefix>_iterate(<prefix>_hash *tb, <prefix>_iterator *iter) */242SH_SCOPE	SH_ELEMENT_TYPE *SH_ITERATE(SH_TYPE * tb, SH_ITERATOR * iter);243 244/* void <prefix>_stat(<prefix>_hash *tb */245SH_SCOPE void SH_STAT(SH_TYPE * tb);246 247#endif							/* SH_DECLARE */248 249 250/* generate implementation of the hash table */251#ifdef SH_DEFINE252 253#ifndef SH_RAW_ALLOCATOR254#include "utils/memutils.h"255#endif256 257/* max data array size,we allow up to PG_UINT32_MAX buckets, including 0 */258#define SH_MAX_SIZE (((uint64) PG_UINT32_MAX) + 1)259 260/* normal fillfactor, unless already close to maximum */261#ifndef SH_FILLFACTOR262#define SH_FILLFACTOR (0.9)263#endif264/* increase fillfactor if we otherwise would error out */265#define SH_MAX_FILLFACTOR (0.98)266/* grow if actual and optimal location bigger than */267#ifndef SH_GROW_MAX_DIB268#define SH_GROW_MAX_DIB 25269#endif270/* grow if more than elements to move when inserting */271#ifndef SH_GROW_MAX_MOVE272#define SH_GROW_MAX_MOVE 150273#endif274#ifndef SH_GROW_MIN_FILLFACTOR275/* but do not grow due to SH_GROW_MAX_* if below */276#define SH_GROW_MIN_FILLFACTOR 0.1277#endif278 279#ifdef SH_STORE_HASH280#define SH_COMPARE_KEYS(tb, ahash, akey, b) (ahash == SH_GET_HASH(tb, b) && SH_EQUAL(tb, b->SH_KEY, akey))281#else282#define SH_COMPARE_KEYS(tb, ahash, akey, b) (SH_EQUAL(tb, b->SH_KEY, akey))283#endif284 285/*286 * Wrap the following definitions in include guards, to avoid multiple287 * definition errors if this header is included more than once.  The rest of288 * the file deliberately has no include guards, because it can be included289 * with different parameters to define functions and types with non-colliding290 * names.291 */292#ifndef SIMPLEHASH_H293#define SIMPLEHASH_H294 295#ifdef FRONTEND296#define sh_error(...) pg_fatal(__VA_ARGS__)297#define sh_log(...) pg_log_info(__VA_ARGS__)298#else299#define sh_error(...) elog(ERROR, __VA_ARGS__)300#define sh_log(...) elog(LOG, __VA_ARGS__)301#endif302 303#endif304 305/*306 * Compute sizing parameters for hashtable. Called when creating and growing307 * the hashtable.308 */309static inline void310SH_COMPUTE_PARAMETERS(SH_TYPE * tb, uint64 newsize)311{312	uint64		size;313 314	/* supporting zero sized hashes would complicate matters */315	size = Max(newsize, 2);316 317	/* round up size to the next power of 2, that's how bucketing works */318	size = pg_nextpower2_64(size);319	Assert(size <= SH_MAX_SIZE);320 321	/*322	 * Verify that allocation of ->data is possible on this platform, without323	 * overflowing Size.324	 */325	if (unlikely((((uint64) sizeof(SH_ELEMENT_TYPE)) * size) >= SIZE_MAX / 2))326		sh_error("hash table too large");327 328	/* now set size */329	tb->size = size;330	tb->sizemask = (uint32) (size - 1);331 332	/*333	 * Compute the next threshold at which we need to grow the hash table334	 * again.335	 */336	if (tb->size == SH_MAX_SIZE)337		tb->grow_threshold = ((double) tb->size) * SH_MAX_FILLFACTOR;338	else339		tb->grow_threshold = ((double) tb->size) * SH_FILLFACTOR;340}341 342/* return the optimal bucket for the hash */343static inline uint32344SH_INITIAL_BUCKET(SH_TYPE * tb, uint32 hash)345{346	return hash & tb->sizemask;347}348 349/* return next bucket after the current, handling wraparound */350static inline uint32351SH_NEXT(SH_TYPE * tb, uint32 curelem, uint32 startelem)352{353	curelem = (curelem + 1) & tb->sizemask;354 355	Assert(curelem != startelem);356 357	return curelem;358}359 360/* return bucket before the current, handling wraparound */361static inline uint32362SH_PREV(SH_TYPE * tb, uint32 curelem, uint32 startelem)363{364	curelem = (curelem - 1) & tb->sizemask;365 366	Assert(curelem != startelem);367 368	return curelem;369}370 371/* return distance between bucket and its optimal position */372static inline uint32373SH_DISTANCE_FROM_OPTIMAL(SH_TYPE * tb, uint32 optimal, uint32 bucket)374{375	if (optimal <= bucket)376		return bucket - optimal;377	else378		return (tb->size + bucket) - optimal;379}380 381static inline uint32382SH_ENTRY_HASH(SH_TYPE * tb, SH_ELEMENT_TYPE * entry)383{384#ifdef SH_STORE_HASH385	return SH_GET_HASH(tb, entry);386#else387	return SH_HASH_KEY(tb, entry->SH_KEY);388#endif389}390 391/* default memory allocator function */392static inline void *SH_ALLOCATE(SH_TYPE * type, Size size);393static inline void SH_FREE(SH_TYPE * type, void *pointer);394 395#ifndef SH_USE_NONDEFAULT_ALLOCATOR396 397/* default memory allocator function */398static inline void *399SH_ALLOCATE(SH_TYPE * type, Size size)400{401#ifdef SH_RAW_ALLOCATOR402	return SH_RAW_ALLOCATOR(size);403#else404	return MemoryContextAllocExtended(type->ctx, size,405									  MCXT_ALLOC_HUGE | MCXT_ALLOC_ZERO);406#endif407}408 409/* default memory free function */410static inline void411SH_FREE(SH_TYPE * type, void *pointer)412{413	pfree(pointer);414}415 416#endif417 418/*419 * Create a hash table with enough space for `nelements` distinct members.420 * Memory for the hash table is allocated from the passed-in context.  If421 * desired, the array of elements can be allocated using a passed-in allocator;422 * this could be useful in order to place the array of elements in a shared423 * memory, or in a context that will outlive the rest of the hash table.424 * Memory other than for the array of elements will still be allocated from425 * the passed-in context.426 */427#ifdef SH_RAW_ALLOCATOR428SH_SCOPE	SH_TYPE *429SH_CREATE(uint32 nelements, void *private_data)430#else431SH_SCOPE	SH_TYPE *432SH_CREATE(MemoryContext ctx, uint32 nelements, void *private_data)433#endif434{435	SH_TYPE    *tb;436	uint64		size;437 438#ifdef SH_RAW_ALLOCATOR439	tb = (SH_TYPE *) SH_RAW_ALLOCATOR(sizeof(SH_TYPE));440#else441	tb = (SH_TYPE *) MemoryContextAllocZero(ctx, sizeof(SH_TYPE));442	tb->ctx = ctx;443#endif444	tb->private_data = private_data;445 446	/* increase nelements by fillfactor, want to store nelements elements */447	size = Min((double) SH_MAX_SIZE, ((double) nelements) / SH_FILLFACTOR);448 449	SH_COMPUTE_PARAMETERS(tb, size);450 451	tb->data = (SH_ELEMENT_TYPE *) SH_ALLOCATE(tb, sizeof(SH_ELEMENT_TYPE) * tb->size);452 453	return tb;454}455 456/* destroy a previously created hash table */457SH_SCOPE void458SH_DESTROY(SH_TYPE * tb)459{460	SH_FREE(tb, tb->data);461	pfree(tb);462}463 464/* reset the contents of a previously created hash table */465SH_SCOPE void466SH_RESET(SH_TYPE * tb)467{468	memset(tb->data, 0, sizeof(SH_ELEMENT_TYPE) * tb->size);469	tb->members = 0;470}471 472/*473 * Grow a hash table to at least `newsize` buckets.474 *475 * Usually this will automatically be called by insertions/deletions, when476 * necessary. But resizing to the exact input size can be advantageous477 * performance-wise, when known at some point.478 */479SH_SCOPE void480SH_GROW(SH_TYPE * tb, uint64 newsize)481{482	uint64		oldsize = tb->size;483	SH_ELEMENT_TYPE *olddata = tb->data;484	SH_ELEMENT_TYPE *newdata;485	uint32		i;486	uint32		startelem = 0;487	uint32		copyelem;488 489	Assert(oldsize == pg_nextpower2_64(oldsize));490	Assert(oldsize != SH_MAX_SIZE);491	Assert(oldsize < newsize);492 493	/* compute parameters for new table */494	SH_COMPUTE_PARAMETERS(tb, newsize);495 496	tb->data = (SH_ELEMENT_TYPE *) SH_ALLOCATE(tb, sizeof(SH_ELEMENT_TYPE) * tb->size);497 498	newdata = tb->data;499 500	/*501	 * Copy entries from the old data to newdata. We theoretically could use502	 * SH_INSERT here, to avoid code duplication, but that's more general than503	 * we need. We neither want tb->members increased, nor do we need to do504	 * deal with deleted elements, nor do we need to compare keys. So a505	 * special-cased implementation is lot faster. As resizing can be time506	 * consuming and frequent, that's worthwhile to optimize.507	 *508	 * To be able to simply move entries over, we have to start not at the509	 * first bucket (i.e olddata[0]), but find the first bucket that's either510	 * empty, or is occupied by an entry at its optimal position. Such a511	 * bucket has to exist in any table with a load factor under 1, as not all512	 * buckets are occupied, i.e. there always has to be an empty bucket.  By513	 * starting at such a bucket we can move the entries to the larger table,514	 * without having to deal with conflicts.515	 */516 517	/* search for the first element in the hash that's not wrapped around */518	for (i = 0; i < oldsize; i++)519	{520		SH_ELEMENT_TYPE *oldentry = &olddata[i];521		uint32		hash;522		uint32		optimal;523 524		if (oldentry->status != SH_STATUS_IN_USE)525		{526			startelem = i;527			break;528		}529 530		hash = SH_ENTRY_HASH(tb, oldentry);531		optimal = SH_INITIAL_BUCKET(tb, hash);532 533		if (optimal == i)534		{535			startelem = i;536			break;537		}538	}539 540	/* and copy all elements in the old table */541	copyelem = startelem;542	for (i = 0; i < oldsize; i++)543	{544		SH_ELEMENT_TYPE *oldentry = &olddata[copyelem];545 546		if (oldentry->status == SH_STATUS_IN_USE)547		{548			uint32		hash;549			uint32		startelem2;550			uint32		curelem;551			SH_ELEMENT_TYPE *newentry;552 553			hash = SH_ENTRY_HASH(tb, oldentry);554			startelem2 = SH_INITIAL_BUCKET(tb, hash);555			curelem = startelem2;556 557			/* find empty element to put data into */558			while (true)559			{560				newentry = &newdata[curelem];561 562				if (newentry->status == SH_STATUS_EMPTY)563				{564					break;565				}566 567				curelem = SH_NEXT(tb, curelem, startelem2);568			}569 570			/* copy entry to new slot */571			memcpy(newentry, oldentry, sizeof(SH_ELEMENT_TYPE));572		}573 574		/* can't use SH_NEXT here, would use new size */575		copyelem++;576		if (copyelem >= oldsize)577		{578			copyelem = 0;579		}580	}581 582	SH_FREE(tb, olddata);583}584 585/*586 * This is a separate static inline function, so it can be reliably be inlined587 * into its wrapper functions even if SH_SCOPE is extern.588 */589static inline SH_ELEMENT_TYPE *590SH_INSERT_HASH_INTERNAL(SH_TYPE * tb, SH_KEY_TYPE key, uint32 hash, bool *found)591{592	uint32		startelem;593	uint32		curelem;594	SH_ELEMENT_TYPE *data;595	uint32		insertdist;596 597restart:598	insertdist = 0;599 600	/*601	 * We do the grow check even if the key is actually present, to avoid602	 * doing the check inside the loop. This also lets us avoid having to603	 * re-find our position in the hashtable after resizing.604	 *605	 * Note that this also reached when resizing the table due to606	 * SH_GROW_MAX_DIB / SH_GROW_MAX_MOVE.607	 */608	if (unlikely(tb->members >= tb->grow_threshold))609	{610		if (unlikely(tb->size == SH_MAX_SIZE))611			sh_error("hash table size exceeded");612 613		/*614		 * When optimizing, it can be very useful to print these out.615		 */616		/* SH_STAT(tb); */617		SH_GROW(tb, tb->size * 2);618		/* SH_STAT(tb); */619	}620 621	/* perform insert, start bucket search at optimal location */622	data = tb->data;623	startelem = SH_INITIAL_BUCKET(tb, hash);624	curelem = startelem;625	while (true)626	{627		uint32		curdist;628		uint32		curhash;629		uint32		curoptimal;630		SH_ELEMENT_TYPE *entry = &data[curelem];631 632		/* any empty bucket can directly be used */633		if (entry->status == SH_STATUS_EMPTY)634		{635			tb->members++;636			entry->SH_KEY = key;637#ifdef SH_STORE_HASH638			SH_GET_HASH(tb, entry) = hash;639#endif640			entry->status = SH_STATUS_IN_USE;641			*found = false;642			return entry;643		}644 645		/*646		 * If the bucket is not empty, we either found a match (in which case647		 * we're done), or we have to decide whether to skip over or move the648		 * colliding entry. When the colliding element's distance to its649		 * optimal position is smaller than the to-be-inserted entry's, we650		 * shift the colliding entry (and its followers) forward by one.651		 */652 653		if (SH_COMPARE_KEYS(tb, hash, key, entry))654		{655			Assert(entry->status == SH_STATUS_IN_USE);656			*found = true;657			return entry;658		}659 660		curhash = SH_ENTRY_HASH(tb, entry);661		curoptimal = SH_INITIAL_BUCKET(tb, curhash);662		curdist = SH_DISTANCE_FROM_OPTIMAL(tb, curoptimal, curelem);663 664		if (insertdist > curdist)665		{666			SH_ELEMENT_TYPE *lastentry = entry;667			uint32		emptyelem = curelem;668			uint32		moveelem;669			int32		emptydist = 0;670 671			/* find next empty bucket */672			while (true)673			{674				SH_ELEMENT_TYPE *emptyentry;675 676				emptyelem = SH_NEXT(tb, emptyelem, startelem);677				emptyentry = &data[emptyelem];678 679				if (emptyentry->status == SH_STATUS_EMPTY)680				{681					lastentry = emptyentry;682					break;683				}684 685				/*686				 * To avoid negative consequences from overly imbalanced687				 * hashtables, grow the hashtable if collisions would require688				 * us to move a lot of entries.  The most likely cause of such689				 * imbalance is filling a (currently) small table, from a690				 * currently big one, in hash-table order.  Don't grow if the691				 * hashtable would be too empty, to prevent quick space692				 * explosion for some weird edge cases.693				 */694				if (unlikely(++emptydist > SH_GROW_MAX_MOVE) &&695					((double) tb->members / tb->size) >= SH_GROW_MIN_FILLFACTOR)696				{697					tb->grow_threshold = 0;698					goto restart;699				}700			}701 702			/* shift forward, starting at last occupied element */703 704			/*705			 * TODO: This could be optimized to be one memcpy in many cases,706			 * excepting wrapping around at the end of ->data. Hasn't shown up707			 * in profiles so far though.708			 */709			moveelem = emptyelem;710			while (moveelem != curelem)711			{712				SH_ELEMENT_TYPE *moveentry;713 714				moveelem = SH_PREV(tb, moveelem, startelem);715				moveentry = &data[moveelem];716 717				memcpy(lastentry, moveentry, sizeof(SH_ELEMENT_TYPE));718				lastentry = moveentry;719			}720 721			/* and fill the now empty spot */722			tb->members++;723 724			entry->SH_KEY = key;725#ifdef SH_STORE_HASH726			SH_GET_HASH(tb, entry) = hash;727#endif728			entry->status = SH_STATUS_IN_USE;729			*found = false;730			return entry;731		}732 733		curelem = SH_NEXT(tb, curelem, startelem);734		insertdist++;735 736		/*737		 * To avoid negative consequences from overly imbalanced hashtables,738		 * grow the hashtable if collisions lead to large runs. The most739		 * likely cause of such imbalance is filling a (currently) small740		 * table, from a currently big one, in hash-table order.  Don't grow741		 * if the hashtable would be too empty, to prevent quick space742		 * explosion for some weird edge cases.743		 */744		if (unlikely(insertdist > SH_GROW_MAX_DIB) &&745			((double) tb->members / tb->size) >= SH_GROW_MIN_FILLFACTOR)746		{747			tb->grow_threshold = 0;748			goto restart;749		}750	}751}752 753/*754 * Insert the key key into the hash-table, set *found to true if the key755 * already exists, false otherwise. Returns the hash-table entry in either756 * case.757 */758SH_SCOPE	SH_ELEMENT_TYPE *759SH_INSERT(SH_TYPE * tb, SH_KEY_TYPE key, bool *found)760{761	uint32		hash = SH_HASH_KEY(tb, key);762 763	return SH_INSERT_HASH_INTERNAL(tb, key, hash, found);764}765 766/*767 * Insert the key key into the hash-table using an already-calculated768 * hash. Set *found to true if the key already exists, false769 * otherwise. Returns the hash-table entry in either case.770 */771SH_SCOPE	SH_ELEMENT_TYPE *772SH_INSERT_HASH(SH_TYPE * tb, SH_KEY_TYPE key, uint32 hash, bool *found)773{774	return SH_INSERT_HASH_INTERNAL(tb, key, hash, found);775}776 777/*778 * This is a separate static inline function, so it can be reliably be inlined779 * into its wrapper functions even if SH_SCOPE is extern.780 */781static inline SH_ELEMENT_TYPE *782SH_LOOKUP_HASH_INTERNAL(SH_TYPE * tb, SH_KEY_TYPE key, uint32 hash)783{784	const uint32 startelem = SH_INITIAL_BUCKET(tb, hash);785	uint32		curelem = startelem;786 787	while (true)788	{789		SH_ELEMENT_TYPE *entry = &tb->data[curelem];790 791		if (entry->status == SH_STATUS_EMPTY)792		{793			return NULL;794		}795 796		Assert(entry->status == SH_STATUS_IN_USE);797 798		if (SH_COMPARE_KEYS(tb, hash, key, entry))799			return entry;800 801		/*802		 * TODO: we could stop search based on distance. If the current803		 * buckets's distance-from-optimal is smaller than what we've skipped804		 * already, the entry doesn't exist. Probably only do so if805		 * SH_STORE_HASH is defined, to avoid re-computing hashes?806		 */807 808		curelem = SH_NEXT(tb, curelem, startelem);809	}810}811 812/*813 * Lookup entry in hash table.  Returns NULL if key not present.814 */815SH_SCOPE	SH_ELEMENT_TYPE *816SH_LOOKUP(SH_TYPE * tb, SH_KEY_TYPE key)817{818	uint32		hash = SH_HASH_KEY(tb, key);819 820	return SH_LOOKUP_HASH_INTERNAL(tb, key, hash);821}822 823/*824 * Lookup entry in hash table using an already-calculated hash.825 *826 * Returns NULL if key not present.827 */828SH_SCOPE	SH_ELEMENT_TYPE *829SH_LOOKUP_HASH(SH_TYPE * tb, SH_KEY_TYPE key, uint32 hash)830{831	return SH_LOOKUP_HASH_INTERNAL(tb, key, hash);832}833 834/*835 * Delete entry from hash table by key.  Returns whether to-be-deleted key was836 * present.837 */838SH_SCOPE bool839SH_DELETE(SH_TYPE * tb, SH_KEY_TYPE key)840{841	uint32		hash = SH_HASH_KEY(tb, key);842	uint32		startelem = SH_INITIAL_BUCKET(tb, hash);843	uint32		curelem = startelem;844 845	while (true)846	{847		SH_ELEMENT_TYPE *entry = &tb->data[curelem];848 849		if (entry->status == SH_STATUS_EMPTY)850			return false;851 852		if (entry->status == SH_STATUS_IN_USE &&853			SH_COMPARE_KEYS(tb, hash, key, entry))854		{855			SH_ELEMENT_TYPE *lastentry = entry;856 857			tb->members--;858 859			/*860			 * Backward shift following elements till either an empty element861			 * or an element at its optimal position is encountered.862			 *863			 * While that sounds expensive, the average chain length is short,864			 * and deletions would otherwise require tombstones.865			 */866			while (true)867			{868				SH_ELEMENT_TYPE *curentry;869				uint32		curhash;870				uint32		curoptimal;871 872				curelem = SH_NEXT(tb, curelem, startelem);873				curentry = &tb->data[curelem];874 875				if (curentry->status != SH_STATUS_IN_USE)876				{877					lastentry->status = SH_STATUS_EMPTY;878					break;879				}880 881				curhash = SH_ENTRY_HASH(tb, curentry);882				curoptimal = SH_INITIAL_BUCKET(tb, curhash);883 884				/* current is at optimal position, done */885				if (curoptimal == curelem)886				{887					lastentry->status = SH_STATUS_EMPTY;888					break;889				}890 891				/* shift */892				memcpy(lastentry, curentry, sizeof(SH_ELEMENT_TYPE));893 894				lastentry = curentry;895			}896 897			return true;898		}899 900		/* TODO: return false; if distance too big */901 902		curelem = SH_NEXT(tb, curelem, startelem);903	}904}905 906/*907 * Delete entry from hash table by entry pointer908 */909SH_SCOPE void910SH_DELETE_ITEM(SH_TYPE * tb, SH_ELEMENT_TYPE * entry)911{912	SH_ELEMENT_TYPE *lastentry = entry;913	uint32		hash = SH_ENTRY_HASH(tb, entry);914	uint32		startelem = SH_INITIAL_BUCKET(tb, hash);915	uint32		curelem;916 917	/* Calculate the index of 'entry' */918	curelem = entry - &tb->data[0];919 920	tb->members--;921 922	/*923	 * Backward shift following elements till either an empty element or an924	 * element at its optimal position is encountered.925	 *926	 * While that sounds expensive, the average chain length is short, and927	 * deletions would otherwise require tombstones.928	 */929	while (true)930	{931		SH_ELEMENT_TYPE *curentry;932		uint32		curhash;933		uint32		curoptimal;934 935		curelem = SH_NEXT(tb, curelem, startelem);936		curentry = &tb->data[curelem];937 938		if (curentry->status != SH_STATUS_IN_USE)939		{940			lastentry->status = SH_STATUS_EMPTY;941			break;942		}943 944		curhash = SH_ENTRY_HASH(tb, curentry);945		curoptimal = SH_INITIAL_BUCKET(tb, curhash);946 947		/* current is at optimal position, done */948		if (curoptimal == curelem)949		{950			lastentry->status = SH_STATUS_EMPTY;951			break;952		}953 954		/* shift */955		memcpy(lastentry, curentry, sizeof(SH_ELEMENT_TYPE));956 957		lastentry = curentry;958	}959}960 961/*962 * Initialize iterator.963 */964SH_SCOPE void965SH_START_ITERATE(SH_TYPE * tb, SH_ITERATOR * iter)966{967	uint64		startelem = PG_UINT64_MAX;968 969	/*970	 * Search for the first empty element. As deletions during iterations are971	 * supported, we want to start/end at an element that cannot be affected972	 * by elements being shifted.973	 */974	for (uint32 i = 0; i < tb->size; i++)975	{976		SH_ELEMENT_TYPE *entry = &tb->data[i];977 978		if (entry->status != SH_STATUS_IN_USE)979		{980			startelem = i;981			break;982		}983	}984 985	/* we should have found an empty element */986	Assert(startelem < SH_MAX_SIZE);987 988	/*989	 * Iterate backwards, that allows the current element to be deleted, even990	 * if there are backward shifts991	 */992	iter->cur = startelem;993	iter->end = iter->cur;994	iter->done = false;995}996 997/*998 * Initialize iterator to a specific bucket. That's really only useful for999 * cases where callers are partially iterating over the hashspace, and that1000 * iteration deletes and inserts elements based on visited entries. Doing that1001 * repeatedly could lead to an unbalanced keyspace when always starting at the1002 * same position.1003 */1004SH_SCOPE void1005SH_START_ITERATE_AT(SH_TYPE * tb, SH_ITERATOR * iter, uint32 at)1006{1007	/*1008	 * Iterate backwards, that allows the current element to be deleted, even1009	 * if there are backward shifts.1010	 */1011	iter->cur = at & tb->sizemask;	/* ensure at is within a valid range */1012	iter->end = iter->cur;1013	iter->done = false;1014}1015 1016/*1017 * Iterate over all entries in the hash-table. Return the next occupied entry,1018 * or NULL if done.1019 *1020 * During iteration the current entry in the hash table may be deleted,1021 * without leading to elements being skipped or returned twice.  Additionally1022 * the rest of the table may be modified (i.e. there can be insertions or1023 * deletions), but if so, there's neither a guarantee that all nodes are1024 * visited at least once, nor a guarantee that a node is visited at most once.1025 */1026SH_SCOPE	SH_ELEMENT_TYPE *1027SH_ITERATE(SH_TYPE * tb, SH_ITERATOR * iter)1028{1029	while (!iter->done)1030	{1031		SH_ELEMENT_TYPE *elem;1032 1033		elem = &tb->data[iter->cur];1034 1035		/* next element in backward direction */1036		iter->cur = (iter->cur - 1) & tb->sizemask;1037 1038		if ((iter->cur & tb->sizemask) == (iter->end & tb->sizemask))1039			iter->done = true;1040		if (elem->status == SH_STATUS_IN_USE)1041		{1042			return elem;1043		}1044	}1045 1046	return NULL;1047}1048 1049/*1050 * Report some statistics about the state of the hashtable. For1051 * debugging/profiling purposes only.1052 */1053SH_SCOPE void1054SH_STAT(SH_TYPE * tb)1055{1056	uint32		max_chain_length = 0;1057	uint32		total_chain_length = 0;1058	double		avg_chain_length;1059	double		fillfactor;1060	uint32		i;1061 1062	uint32	   *collisions = (uint32 *) palloc0(tb->size * sizeof(uint32));1063	uint32		total_collisions = 0;1064	uint32		max_collisions = 0;1065	double		avg_collisions;1066 1067	for (i = 0; i < tb->size; i++)1068	{1069		uint32		hash;1070		uint32		optimal;1071		uint32		dist;1072		SH_ELEMENT_TYPE *elem;1073 1074		elem = &tb->data[i];1075 1076		if (elem->status != SH_STATUS_IN_USE)1077			continue;1078 1079		hash = SH_ENTRY_HASH(tb, elem);1080		optimal = SH_INITIAL_BUCKET(tb, hash);1081		dist = SH_DISTANCE_FROM_OPTIMAL(tb, optimal, i);1082 1083		if (dist > max_chain_length)1084			max_chain_length = dist;1085		total_chain_length += dist;1086 1087		collisions[optimal]++;1088	}1089 1090	for (i = 0; i < tb->size; i++)1091	{1092		uint32		curcoll = collisions[i];1093 1094		if (curcoll == 0)1095			continue;1096 1097		/* single contained element is not a collision */1098		curcoll--;1099		total_collisions += curcoll;1100		if (curcoll > max_collisions)1101			max_collisions = curcoll;1102	}1103 1104	/* large enough to be worth freeing, even if just used for debugging */1105	pfree(collisions);1106 1107	if (tb->members > 0)1108	{1109		fillfactor = tb->members / ((double) tb->size);1110		avg_chain_length = ((double) total_chain_length) / tb->members;1111		avg_collisions = ((double) total_collisions) / tb->members;1112	}1113	else1114	{1115		fillfactor = 0;1116		avg_chain_length = 0;1117		avg_collisions = 0;1118	}1119 1120	sh_log("size: " UINT64_FORMAT ", members: %u, filled: %f, total chain: %u, max chain: %u, avg chain: %f, total_collisions: %u, max_collisions: %u, avg_collisions: %f",1121		   tb->size, tb->members, fillfactor, total_chain_length, max_chain_length, avg_chain_length,1122		   total_collisions, max_collisions, avg_collisions);1123}1124 1125#endif							/* SH_DEFINE */1126 1127 1128/* undefine external parameters, so next hash table can be defined */1129#undef SH_PREFIX1130#undef SH_KEY_TYPE1131#undef SH_KEY1132#undef SH_ELEMENT_TYPE1133#undef SH_HASH_KEY1134#undef SH_SCOPE1135#undef SH_DECLARE1136#undef SH_DEFINE1137#undef SH_GET_HASH1138#undef SH_STORE_HASH1139#undef SH_USE_NONDEFAULT_ALLOCATOR1140#undef SH_EQUAL1141 1142/* undefine locally declared macros */1143#undef SH_MAKE_PREFIX1144#undef SH_MAKE_NAME1145#undef SH_MAKE_NAME_1146#undef SH_FILLFACTOR1147#undef SH_MAX_FILLFACTOR1148#undef SH_GROW_MAX_DIB1149#undef SH_GROW_MAX_MOVE1150#undef SH_GROW_MIN_FILLFACTOR1151#undef SH_MAX_SIZE1152 1153/* types */1154#undef SH_TYPE1155#undef SH_STATUS1156#undef SH_STATUS_EMPTY1157#undef SH_STATUS_IN_USE1158#undef SH_ITERATOR1159 1160/* external function names */1161#undef SH_CREATE1162#undef SH_DESTROY1163#undef SH_RESET1164#undef SH_INSERT1165#undef SH_INSERT_HASH1166#undef SH_DELETE_ITEM1167#undef SH_DELETE1168#undef SH_LOOKUP1169#undef SH_LOOKUP_HASH1170#undef SH_GROW1171#undef SH_START_ITERATE1172#undef SH_START_ITERATE_AT1173#undef SH_ITERATE1174#undef SH_ALLOCATE1175#undef SH_FREE1176#undef SH_STAT1177 1178/* internal function names */1179#undef SH_COMPUTE_PARAMETERS1180#undef SH_COMPARE_KEYS1181#undef SH_INITIAL_BUCKET1182#undef SH_NEXT1183#undef SH_PREV1184#undef SH_DISTANCE_FROM_OPTIMAL1185#undef SH_ENTRY_HASH1186#undef SH_INSERT_HASH_INTERNAL1187#undef SH_LOOKUP_HASH_INTERNAL1188 
codekingpro/portable-devtools · Team Ai