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 + -
显示快捷键?