dynahash.c

来自「postgresql8.3.4源码,开源数据库」· C语言 代码 · 共 1,529 行 · 第 1/3 页

C
1,529
字号
 * arrays */static boolinit_htab(HTAB *hashp, long nelem){	HASHHDR    *hctl = hashp->hctl;	HASHSEGMENT *segp;	long		lnbuckets;	int			nbuckets;	int			nsegs;	/*	 * initialize mutex if it's a partitioned table	 */	if (IS_PARTITIONED(hctl))		SpinLockInit(&hctl->mutex);	/*	 * Divide number of elements by the fill factor to determine a desired	 * number of buckets.  Allocate space for the next greater power of two	 * number of buckets	 */	lnbuckets = (nelem - 1) / hctl->ffactor + 1;	nbuckets = 1 << my_log2(lnbuckets);	/*	 * In a partitioned table, nbuckets must be at least equal to	 * num_partitions; were it less, keys with apparently different partition	 * numbers would map to the same bucket, breaking partition independence.	 * (Normally nbuckets will be much bigger; this is just a safety check.)	 */	while (nbuckets < hctl->num_partitions)		nbuckets <<= 1;	hctl->max_bucket = hctl->low_mask = nbuckets - 1;	hctl->high_mask = (nbuckets << 1) - 1;	/*	 * Figure number of directory segments needed, round up to a power of 2	 */	nsegs = (nbuckets - 1) / hctl->ssize + 1;	nsegs = 1 << my_log2(nsegs);	/*	 * Make sure directory is big enough. If pre-allocated directory is too	 * small, choke (caller screwed up).	 */	if (nsegs > hctl->dsize)	{		if (!(hashp->dir))			hctl->dsize = nsegs;		else			return false;	}	/* Allocate a directory */	if (!(hashp->dir))	{		CurrentDynaHashCxt = hashp->hcxt;		hashp->dir = (HASHSEGMENT *)			hashp->alloc(hctl->dsize * sizeof(HASHSEGMENT));		if (!hashp->dir)			return false;	}	/* Allocate initial segments */	for (segp = hashp->dir; hctl->nsegs < nsegs; hctl->nsegs++, segp++)	{		*segp = seg_alloc(hashp);		if (*segp == NULL)			return false;	}	/* Choose number of entries to allocate at a time */	hctl->nelem_alloc = choose_nelem_alloc(hctl->entrysize);#if HASH_DEBUG	fprintf(stderr, "init_htab:\n%s%p\n%s%ld\n%s%ld\n%s%d\n%s%ld\n%s%u\n%s%x\n%s%x\n%s%ld\n%s%ld\n",			"TABLE POINTER   ", hashp,			"DIRECTORY SIZE  ", hctl->dsize,			"SEGMENT SIZE    ", hctl->ssize,			"SEGMENT SHIFT   ", hctl->sshift,			"FILL FACTOR     ", hctl->ffactor,			"MAX BUCKET      ", hctl->max_bucket,			"HIGH MASK       ", hctl->high_mask,			"LOW  MASK       ", hctl->low_mask,			"NSEGS           ", hctl->nsegs,			"NENTRIES        ", hctl->nentries);#endif	return true;}/* * Estimate the space needed for a hashtable containing the given number * of entries of given size. * NOTE: this is used to estimate the footprint of hashtables in shared * memory; therefore it does not count HTAB which is in local memory. * NB: assumes that all hash structure parameters have default values! */Sizehash_estimate_size(long num_entries, Size entrysize){	Size		size;	long		nBuckets,				nSegments,				nDirEntries,				nElementAllocs,				elementSize,				elementAllocCnt;	/* estimate number of buckets wanted */	nBuckets = 1L << my_log2((num_entries - 1) / DEF_FFACTOR + 1);	/* # of segments needed for nBuckets */	nSegments = 1L << my_log2((nBuckets - 1) / DEF_SEGSIZE + 1);	/* directory entries */	nDirEntries = DEF_DIRSIZE;	while (nDirEntries < nSegments)		nDirEntries <<= 1;		/* dir_alloc doubles dsize at each call */	/* fixed control info */	size = MAXALIGN(sizeof(HASHHDR));	/* but not HTAB, per above */	/* directory */	size = add_size(size, mul_size(nDirEntries, sizeof(HASHSEGMENT)));	/* segments */	size = add_size(size, mul_size(nSegments,								MAXALIGN(DEF_SEGSIZE * sizeof(HASHBUCKET))));	/* elements --- allocated in groups of choose_nelem_alloc() entries */	elementAllocCnt = choose_nelem_alloc(entrysize);	nElementAllocs = (num_entries - 1) / elementAllocCnt + 1;	elementSize = MAXALIGN(sizeof(HASHELEMENT)) + MAXALIGN(entrysize);	size = add_size(size,					mul_size(nElementAllocs,							 mul_size(elementAllocCnt, elementSize)));	return size;}/* * Select an appropriate directory size for a hashtable with the given * maximum number of entries. * This is only needed for hashtables in shared memory, whose directories * cannot be expanded dynamically. * NB: assumes that all hash structure parameters have default values! * * XXX this had better agree with the behavior of init_htab()... */longhash_select_dirsize(long num_entries){	long		nBuckets,				nSegments,				nDirEntries;	/* estimate number of buckets wanted */	nBuckets = 1L << my_log2((num_entries - 1) / DEF_FFACTOR + 1);	/* # of segments needed for nBuckets */	nSegments = 1L << my_log2((nBuckets - 1) / DEF_SEGSIZE + 1);	/* directory entries */	nDirEntries = DEF_DIRSIZE;	while (nDirEntries < nSegments)		nDirEntries <<= 1;		/* dir_alloc doubles dsize at each call */	return nDirEntries;}/* * Compute the required initial memory allocation for a shared-memory * hashtable with the given parameters.  We need space for the HASHHDR * and for the (non expansible) directory. */Sizehash_get_shared_size(HASHCTL *info, int flags){	Assert(flags & HASH_DIRSIZE);	Assert(info->dsize == info->max_dsize);	return sizeof(HASHHDR) + info->dsize * sizeof(HASHSEGMENT);}/********************** DESTROY ROUTINES ************************/voidhash_destroy(HTAB *hashp){	if (hashp != NULL)	{		/* allocation method must be one we know how to free, too */		Assert(hashp->alloc == DynaHashAlloc);		/* so this hashtable must have it's own context */		Assert(hashp->hcxt != NULL);		hash_stats("destroy", hashp);		/*		 * Free everything by destroying the hash table's memory context.		 */		MemoryContextDelete(hashp->hcxt);	}}voidhash_stats(const char *where, HTAB *hashp){#if HASH_STATISTICS	fprintf(stderr, "%s: this HTAB -- accesses %ld collisions %ld\n",			where, hashp->hctl->accesses, hashp->hctl->collisions);	fprintf(stderr, "hash_stats: entries %ld keysize %ld maxp %u segmentcount %ld\n",			hashp->hctl->nentries, (long) hashp->hctl->keysize,			hashp->hctl->max_bucket, hashp->hctl->nsegs);	fprintf(stderr, "%s: total accesses %ld total collisions %ld\n",			where, hash_accesses, hash_collisions);	fprintf(stderr, "hash_stats: total expansions %ld\n",			hash_expansions);#endif}/*******************************SEARCH ROUTINES *****************************//* * get_hash_value -- exported routine to calculate a key's hash value * * We export this because for partitioned tables, callers need to compute * the partition number (from the low-order bits of the hash value) before * searching. */uint32get_hash_value(HTAB *hashp, const void *keyPtr){	return hashp->hash(keyPtr, hashp->keysize);}/* Convert a hash value to a bucket number */static inline uint32calc_bucket(HASHHDR *hctl, uint32 hash_val){	uint32		bucket;	bucket = hash_val & hctl->high_mask;	if (bucket > hctl->max_bucket)		bucket = bucket & hctl->low_mask;	return bucket;}/* * hash_search -- look up key in table and perform action * hash_search_with_hash_value -- same, with key's hash value already computed * * action is one of: *		HASH_FIND: look up key in table *		HASH_ENTER: look up key in table, creating entry if not present *		HASH_ENTER_NULL: same, but return NULL if out of memory *		HASH_REMOVE: look up key in table, remove entry if present * * Return value is a pointer to the element found/entered/removed if any, * or NULL if no match was found.  (NB: in the case of the REMOVE action, * the result is a dangling pointer that shouldn't be dereferenced!) * * HASH_ENTER will normally ereport a generic "out of memory" error if * it is unable to create a new entry.	The HASH_ENTER_NULL operation is * the same except it will return NULL if out of memory.  Note that * HASH_ENTER_NULL cannot be used with the default palloc-based allocator, * since palloc internally ereports on out-of-memory. * * If foundPtr isn't NULL, then *foundPtr is set TRUE if we found an * existing entry in the table, FALSE otherwise.  This is needed in the * HASH_ENTER case, but is redundant with the return value otherwise. * * For hash_search_with_hash_value, the hashvalue parameter must have been * calculated with get_hash_value(). */void *hash_search(HTAB *hashp,			const void *keyPtr,			HASHACTION action,			bool *foundPtr){	return hash_search_with_hash_value(hashp,									   keyPtr,									   hashp->hash(keyPtr, hashp->keysize),									   action,									   foundPtr);}void *hash_search_with_hash_value(HTAB *hashp,							const void *keyPtr,							uint32 hashvalue,							HASHACTION action,							bool *foundPtr){	HASHHDR    *hctl = hashp->hctl;	Size		keysize;	uint32		bucket;	long		segment_num;	long		segment_ndx;	HASHSEGMENT segp;	HASHBUCKET	currBucket;	HASHBUCKET *prevBucketPtr;	HashCompareFunc match;#if HASH_STATISTICS	hash_accesses++;	hctl->accesses++;#endif	/*	 * Do the initial lookup	 */	bucket = calc_bucket(hctl, hashvalue);	segment_num = bucket >> hashp->sshift;	segment_ndx = MOD(bucket, hashp->ssize);	segp = hashp->dir[segment_num];	if (segp == NULL)		hash_corrupted(hashp);	prevBucketPtr = &segp[segment_ndx];	currBucket = *prevBucketPtr;	/*	 * Follow collision chain looking for matching key	 */	match = hashp->match;		/* save one fetch in inner loop */	keysize = hashp->keysize;	/* ditto */	while (currBucket != NULL)	{		if (currBucket->hashvalue == hashvalue &&			match(ELEMENTKEY(currBucket), keyPtr, keysize) == 0)			break;		prevBucketPtr = &(currBucket->link);		currBucket = *prevBucketPtr;#if HASH_STATISTICS		hash_collisions++;		hctl->collisions++;#endif	}	if (foundPtr)		*foundPtr = (bool) (currBucket != NULL);	/*	 * OK, now what?	 */	switch (action)	{		case HASH_FIND:			if (currBucket != NULL)				return (void *) ELEMENTKEY(currBucket);			return NULL;		case HASH_REMOVE:			if (currBucket != NULL)			{				/* use volatile pointer to prevent code rearrangement */				volatile HASHHDR *hctlv = hctl;				/* if partitioned, must lock to touch nentries and freeList */				if (IS_PARTITIONED(hctlv))					SpinLockAcquire(&hctlv->mutex);				Assert(hctlv->nentries > 0);				hctlv->nentries--;				/* remove record from hash bucket's chain. */				*prevBucketPtr = currBucket->link;				/* add the record to the freelist for this table.  */				currBucket->link = hctlv->freeList;				hctlv->freeList = currBucket;				if (IS_PARTITIONED(hctlv))					SpinLockRelease(&hctlv->mutex);				/*				 * better hope the caller is synchronizing access to this				 * element, because someone else is going to reuse it the next				 * time something is added to the table				 */				return (void *) ELEMENTKEY(currBucket);			}			return NULL;		case HASH_ENTER_NULL:			/* ENTER_NULL does not work with palloc-based allocator */			Assert(hashp->alloc != DynaHashAlloc);			/* FALL THRU */		case HASH_ENTER:			/* Return existing element if found, else create one */			if (currBucket != NULL)				return (void *) ELEMENTKEY(currBucket);			/* disallow inserts if frozen */			if (hashp->frozen)				elog(ERROR, "cannot insert into frozen hashtable \"%s\"",					 hashp->tabname);			currBucket = get_hash_entry(hashp);			if (currBucket == NULL)			{				/* out of memory */				if (action == HASH_ENTER_NULL)					return NULL;				/* report a generic message */				if (hashp->isshared)					ereport(ERROR,							(errcode(ERRCODE_OUT_OF_MEMORY),							 errmsg("out of shared memory")));				else					ereport(ERROR,							(errcode(ERRCODE_OUT_OF_MEMORY),							 errmsg("out of memory")));			}			/* link into hashbucket chain */			*prevBucketPtr = currBucket;			currBucket->link = NULL;			/* copy key into record */			currBucket->hashvalue = hashvalue;			hashp->keycopy(ELEMENTKEY(currBucket), keyPtr, keysize);			/* caller is expected to fill the data field on return */			/*			 * Check if it is time to split a bucket.  Can't split if running			 * in partitioned mode, nor if table is the subject of any active			 * hash_seq_search scans.  Strange order of these tests is to try			 * to check cheaper conditions first.			 */			if (!IS_PARTITIONED(hctl) &&			hctl->nentries / (long) (hctl->max_bucket + 1) >= hctl->ffactor &&				!has_seq_scans(hashp))			{				/*				 * NOTE: failure to expand table is not a fatal error, it just				 * means we have to run at higher fill factor than we wanted.				 */				expand_table(hashp);			}			return (void *) ELEMENTKEY(currBucket);	}	elog(ERROR, "unrecognized hash action code: %d", (int) action);	return NULL;				/* keep compiler quiet */}/* * create a new entry if possible */static HASHBUCKETget_hash_entry(HTAB *hashp){	/* use volatile pointer to prevent code rearrangement */	volatile HASHHDR *hctlv = hashp->hctl;	HASHBUCKET	newElement;	for (;;)	{		/* if partitioned, must lock to touch nentries and freeList */		if (IS_PARTITIONED(hctlv))			SpinLockAcquire(&hctlv->mutex);		/* try to get an entry from the freelist */		newElement = hctlv->freeList;		if (newElement != NULL)			break;		/* no free elements.  allocate another chunk of buckets */		if (IS_PARTITIONED(hctlv))			SpinLockRelease(&hctlv->mutex);		if (!element_alloc(hashp, hctlv->nelem_alloc))		{			/* out of memory */			return NULL;		}	}	/* remove entry from freelist, bump nentries */	hctlv->freeList = newElement->link;	hctlv->nentries++;	if (IS_PARTITIONED(hctlv))		SpinLockRelease(&hctlv->mutex);	return newElement;}/* * hash_get_num_entries -- get the number of entries in a hashtable */longhash_get_num_entries(HTAB *hashp){	/*	 * We currently don't bother with the mutex; it's only sensible to call	 * this function if you've got lock on all partitions of the table.	 */	return hashp->hctl->nentries;}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?